I have a UITableView added to a UIViewController inside a UIView. The table view has constraints to fit the size of it's parent view. The view has constraints for it's position and size. If I animate the size of this view, the table view shows new rows, but the new elements, which about to appear, seem to fly around from their initial (not set) position. For example, the right detail indicator of the UITableViewCell flies from the left edge of the cell, the text labels from a slightly different position, etc.
I do the animation like this:
[self.tableView reloadData];
[UIView animateWithDuration:0.3f animations:^{
self.tableViewHeightConstraint.constant = blah;
[self.view layoutIfNeeded];
}];
As you can see, the data reloading takes place before the animation block. Once the animation was played, changing the view size does not animate the table view's contents anymore (I guess it does, but everything is in their place already.)
How can I prevent animating the elements and keep the animation of the frame?
Have a look at calling setAnimationsEnabled on the cells or maybe the cells accessory view.
Related
I have already built out 2 collection views, 1 horizontally scrolling (at the top) & 1 vertically scrolling (at the middle - bottom) that are used to view 2 different sets of content in my Objective-C iOS application similar to this Instagram screenshot:
I am trying to add functionality to make it so that the horizontally scrolling Collection View disappears when the user scrolls up on the vertically scrolling one. What is the best way to accomplish this task? I have looked up tutorials on adding a collection view in another collection view's cell but I cant find anything on just adding a collection view to the 1st cell of another collection view. What would be the best way to accomplish this functionality?
I think you should you use UITableView with UICollectionView. On the screeshot, I think horizontal collectionview is embedded in first cell of the tableview. And when use starts to scroll tableview, first row is gone as you want.
Edit
Create uitableview with 2 prototype cells. Create horizontall collectionview and embed it in first cell of tableview, this is first prototype cell. Then create second prototype cell for images. And when user will start scroll the tableview first cell will gone, as you want.
If you don't want to go with the other suggested method of embedding the horizontal collection view into the top cell of the vertical collection view, you could use the vertical collection view's scrolling callbacks (scrollViewDidScroll, since UICollectionView subclasses UIScrollView). When the vertical collection view scrolls, you can apply a transform to the top collection view to move it off the top of the screen based on the contentOffset of the vertical collection view, and then have it reappear once the contentOffset approaches 0.
Keep in mind that with this approach, the vertical collection view's frame will likely be the height of the screen minus the height of the horizontal collection view. Therefore, you will need a bit of extra logic to expand the vertical collection view's frame to take up the whole screen once the horizontal collection view has disappeared from sight. Otherwise, you will have an awkward blank bar at the top of the screen where the horizontal collection view initially was while you scroll.
You have two scroll view lets call it "cvHorizontal" and "cvVertical".
You can manage scrollViewDidScroll method to hide cvHorizontal when scrolled up and show cvHorizontal when scrolled down.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
if (lastContentOffset > scrollView.contentOffset.y){
[self showCategory:YES];
lastContentOffset = scrollView.contentOffset.y;
} else if (lastContentOffset < scrollView.contentOffset.y) {
[self showCategory:NO];
lastContentOffset = scrollView.contentOffset.y;
}
}
-(void)showCategory:(BOOL)flag{
if(flag){
[UIView animateWithDuration:0.6 animations:^{
if(cvHorizontal.hidden ){
cvHorizontal.hidden=NO;
cvHorizontalHeight.constant=65.0f;//manage the cvHorizontal height and all the other constraints calculation if any
}
}];
}else {
[UIView animateWithDuration:0.6 animations:^{
cvHorizontal.hidden=YES;
cvHorizontalHeight.constant=0;
}];
}
}
I have a UIView called containerView.
I add this as a subview to a controller view's root view. I have programmatically added a few constraints to it (I centered it and made the width a few points from the superview's width).
I have added a few UILabels to the containerView as subviews. The height of the UILabels dictate the height of the containerView.
When the user taps the screen, the containerView is moved up from CGRectOffset() and once the animation is complete, it is moved back to the original position.
CGPoint absolutePoint = self.containterView.frame.origin;
self.containerYConstraint.constant = -absolutePoint;
[UIView
animateWithDuration:0.5
animations:^
{
[viewForUpdate setNeedsUpdateConstraints];
}
completion:^(BOOL finished)
{
self.containerYConstraint.constant = 0;
[viewForUpdate setNeedsUpdateConstraints];
[self.containerView.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)]; // Remove all subviews
}];
I need to remove the UILabels I have put in there as subviews and replace them with different labels. However, the moment I remove one of the UILabel's the entire containerView goes missing (I set the background as red so I can see it). I remove all the subviews in the example but when I try to just remove one the same effect occurs.
Why does this occur? Does this have something to do with auto layout? Also if I want to recenter it after I remove one of the UILabels, how do I re-do the constraints?
Modifying frames when using AutoLayout is a no-no. Once you begin using AutoLayout you're effectively telling the system that you want it to set the frames for you.
Instead of animating the frame directly, create properties pointing to your constraints and animate those constraints.
I have tableview and several controls (text fields, buttons and labels) beneath it.
When keyboard shows up I can either reduce parent view height or slide it up so my controls are accessible.
That part works fine.
However I want to adjust tableview also, so I tried reducing its height (if I reduce the height of the parent view) or moving down the origin.y coordinate (if I slide parent view up).
Neither worked, i.e. tableView would not change to the new frame, it stays the same. Tableview only resizes if I do not manipulate parent view, i.e. if I adjust tableView frame alone.
Here is the methods for that:
-(void)resizeTbl:(int)pnts{
CGRect tblframe = self.myTable.frame;
//tblframe.origin.y+=pnts;
tblframe.size.height-=pnts;
self.myTable.frame =tblframe;}
-(void )keyboardWillShow:(NSNotification *)notif{
int pnts=160;
CGRect frame = self.view.frame;
//frame.origin.y-=pnts;
frame.size.height-=pnts;
self.view.frame = frame;
[self resizeTbl:pnts];}
I probably could move all the controls up one by one and then table resize would work but I think there should be an easier way. Any advice would be greatly appreciated.
Update/workaround:
Since table resize works alone just fine, I added one more observer - keyboardDidShow, and moved resize myTable from keyboardWilShow into there to take care of tableview after keyboard is up.
-(void)keyboardDidShow:(NSNotification *)notif{
[self resizeTbl:160];}
So all the views come up as they are supposed to now.
However, when focus moves from one textbox to another with the keyboard already up, tableView resizes to its original frame by itself and I cannot catch when and how that does it. And then, when keyboard goes down tableView expands beyond its original frame, because I must have
-(void)keyboardDidHide:(NSNotification *)notif{
[self resizeTbl:-160];}
However, when focus changes again tableView shrinks back to its normal frame and everything looks just fine. If I could somehow prevent those unwanted tableview resizes that mess things up.
If someone could makes sense out all of this I would be very appreciative.
Update:
I got it. Autolayout was messing me up. I turned it off like that and my logic now works, no glitches.
What is the autoResizingMask of the view set to?
Try this when you initialize the table view.
self.myTable.autoresizingMask = UIViewAutoresizingFlexibleHeight;
This will cause the table view to be resized with its parent view.
You can't change self.view.frame. You should have a container view above self.view that holds self.myTable and all other controls you want. self.myTable and other controls should have the correct autoresizingMask.
When keyboard is shown, you resize the container view and all other views will respect their autoresizingMasks.
-(void )keyboardWillShow:(NSNotification *)notif{
int pnts=160;
CGRect rect = self.view.bounds;
rect.size.height-=pnts;
self.containerView.frame = rect;
// the following should not be needed if self.myTableView.autoresizingMask
// is at least UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleTopMargin
// but you can try adding this
self.myTableView.frame = self.containerView.bounds;
}
I have a UIScrollView which contains a UIView and a UITableView. My goal is to adjust the height of the UIScrollView to allow me to scroll the contents of the UIScrollView to a specific point.
Here is my view: It has a UIView up top and a UITableView down below.
When I scroll, I want the UIView to stop at a specific point like so:
The tableView would be able to continue scrolling, but the UIView would be locked in place until the user scrolled up and brought the UIView back to its original state.
A prime example of what I am trying to do is the AppStore.app on iOS 6. When you view the details of the app, the filter bar for Details, Reviews and Related moves to the top of the screen and stops. I hope this all made sense.
Thanks
I ended up going with a simpler approach. can't believe I didn't see this before. I created two views, one for the UITableView's tableHeaderView and one for the viewForHeaderInSection. The view I wanted to remain visible at all times is placed in the viewForHeaderInSection method and the other view is placed in the tableHeaderView property. This is a much simpler approach, I think than using a scrollview. The only issue I have run into with this approach is all my UIView animations in these two views no longer animate.
Here is my code.
[self.tableView setTableHeaderView:self.headerView];
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
return self.tableViewHeader;
}
add yourself as a UIScrollViewDelegate to the UITableView and implement the - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView so that if your views are in their starter positions they do this:
- your UITableView animates its size to the second state:
[UIView animateWithDuration:.1f animations:^{
CGRect theFrame = myView.frame;
theFrame.size.height += floatOfIncreasedHeight;
myView.frame = theFrame;
}];
- your UIView animates its vertical movement
[UIView animateWithDuration:3 delay:0 options:UIViewAnimationOptionCurveLinear animations:^(void){
view.center = CGPointMake(view.center.x , view.center.y + floatOfVerticalMovement);
}completion:^(BOOL Finished){
view.center = CGPointMake(view.center.x , view.center.y - floatOfVerticalMovement);]
Finally always in the delegate implement – scrollViewDidScrollToTop: so that you know can animate back to the initial state (using the same techniques reversed).
UPDATE:
since your views are inside a scroll view, there is a simpler way if you are ok with the table view being partly out of bounds in your starter position (i.e. instead of changing size it just scrolls into view):
make the scroll view frame size as big as your final tableview + your initial (entire) view and place it at 0,0 (so its final part will be hidden outside of the screen)
scrollview.frame = CGRectMake(0,0,tableview.frame.size.width,tableview.frame.size.height + view.frame.size.height);
you make the container scrollview contents as big as the entire table view + the entire view + the amount of the view that you want out of the way when scrolling the table view.
scrollview.contentSize = CGSizeMake(scrollview.frame.size.width, tableview.frame.size.height + view.frame.size.height + floatOfViewHeightIWantOutOfTheWay);
you place the view one after the other in the scrollview leaving all the additional empty space after the table view
view.frame = CGRectMake(0,0,view.frame.size.width, view.frame.size.height);
tableview.frame = CGRectMake(0,view.frame.size.height, tableview.frame.size.width, tableview.frame.size.height);
now it should just work because since iOS 3 nested scrolling is supported
You can easily achieve this by setting the content size of the scrollView correctly and keep the height of the UITableView smaller than your viewcontroller's height, so that it fits the bottom part of the top UIView and the UITableView...
Another scenario is to split the top View in 2 parts.
The part that will scroll away and the part that will be visible.
Then set the part that will scroll away as the entire UITableView header and the part that will remain visible as the header view for the first table section.
So then you can achieve this with a single UITableView, without having to use a UIScrollView
What you're looking for is something like what Game Center happens to do with it's header which can actually be modelled with a table header, a custom section header view, and some very clever calculations that never actually involve messing with the frame and bounds of the table.
First, the easy part: faking a sticky view. That "view that's always present when scrolling the table" implemented as a section header. By making the number of sections in the table 1, and implementing -headerViewForSection:, it's possible to seamlessly make the view scroll with the tableview all for free (API-wise that is):
- (UITableViewHeaderFooterView *)headerViewForSection:(NSInteger)section {
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0,0,320,50)];
label.text = #"Info that was always present when scrolling the UITableView";
label.textAlignment = UITextAlignmentCenter;
label.backgroundColor = [UIColor colorWithRed:0.243 green:0.250 blue:0.253 alpha:1.000];
label.textColor = UIColor.whiteColor;
return label;
}
Finally, the hard part: KVO. When the table scrolls, we have to keep the header up there sticky with regards to the top of the view's frame, which means that you can KVO contentOffset, and use the resultant change in value to approximate the frame that the view should stick to with a little MIN() magic. Assuming your header is 44 pixels tall, the code below calculates the appropriate frame value:
CGPoint offset = [contentOffsetChange CGPointValue];
[self.tableView layoutSubviews];
self.tableView.tableHeaderView.frame = CGRectMake(0,MIN(0,offset.y),CGRectGetWidth(self.scrollView.frame),44);
If the above is infeasible, SMHeadedList actually has a fairly great, and little known, example of how complicated it can be to implement a "double tableview". That implementation has the added benefit of allowing the "header" tableview to scroll with the "main" tableview.
For future visitors, I've implemented a much simpler version, albeit one that accomplishes the goal with Reactive Cocoa, and a little bit of a different outcome. Even so, I believe it may be relevant.
What if you break the UIView into the top and bottom. The bottom will be the info.
Set UITableView.tableHeaderView = topView in viewDidLoad
and the return bottomView as Section Header in delegate method to make it float:
(UITableViewHeaderFooterView *)headerViewForSection:(NSInteger)section
{
return bottomView;
}
Just using the UITableView can solve with your problem. it is not need to use another scroll view.
set your view as the header view of UITableView. Then add your present view to the header view.
complete - (void)scrollViewDidScroll:(UIScrollView *)scrollView; . Tn the function to check the contentoffset of scroll view, and set the present view's frame.
I currently have a view controller that is comprised of a Navigation bar, followed by a UIView that has two UIButtons added as subViews. There is then a UITableView underneath that begins at the bottom of the container UIView.
At the moment, when the user scrolls the UITableView it goes behind the UIView and UIButtons. What I actually want to happen is for the UIView and UIButtons to move up with the table view but only by the value of their height which in this case is 58 pixels. The flow would be like this...
1) Table scrolls and the UIView moves with it for the first 58 pixels.
2) The user continues to scroll the table but the UIView "pins" itself just out of view under the navigation bar.
3) When the user scrolls the table back down the UIView is then picked up and dragged back into view. I believe the new Facebook app does something similar in the timeline.
I don't want to set the UIView as the TableHeaderView of the table as I also have a pull-to-refresh which then sits above the buttons and looks terrible. I've tried playing around with the contentOffset properties of the underlying scrollview of the table but have hit a brick wall.
Any advice on where to start would be appreciated.
Thanks
EDIT: I am gotten a little further and using this code to move the frame of the UIView.
-(void) scrollViewDidScroll:(UIScrollView *)scrollView
{
NSLog (#"Content Offset: %f", self.tableView.contentOffset.y);
NSLog (#"Button Frame: %f", self.btnBackground.frame.origin.y);
if (self.tableView.contentOffset.y > 0)
{
CGRect newFrame = self.btnBackground.frame;
newFrame.origin.x = 0;
newFrame.origin.y = -self.tableView.contentOffset.y;
[self.btnBackground setFrame: newFrame];
}
}
The problem now is that the scrollViewDidScroll delegate method doesn't get fired quickly enough if the table view is scrolled fast. The result is that the UIView doesn't quite make all way back to its original position when scroll quickly.
The scroll content offset is a good idea. Also if you tableview has only one section one approach is to do a custom header view representing the top level widgets. If there is more than one sections create an additional empty section which would return your custom header.
You can refer to this stack overflow post.
Customize UITableview Header Section
Well Asked Question (y)
well , for me i would first : use a main UIScrollView that contains both your topView and the tableView under it and that has the same width as your top UIView and UITableView and set its height to be height(tableView) + height(topView).
Second : since UITableView is a subClass of UISCrollView you can use scrollViewDidScroll delegate to know if the tableview is scrolled up or down.
in this cas you will have Two cases :
1) tableview is scrolled up = > you set the content offset of the main scrollView to be
[scrollView setContentOffset:CGPointMake(0, 58) animated:YES];
2) when the table view is scrolled down you can reset the content offset again
[scrollView setContentOffset:CGPointMake(0, 0) animated:YES];