I have a UITableViewController with a UIView at the bottom. (using storyboard). My UIView is set to hidden and changes state afterwards on click of button. First, I was trying to resize (increase height basically) my UIView on IBAction(buttonClick) using self.concluidoView.frame = CGRectMake(0, 0, 320, 50); but UIView was disappearing instead.
Now, it is correctly expanding UIView with the following code inside IBAction:
[UIView animateWithDuration:1.0
animations:^{
CGRect frame = self.concluidoView.frame;
frame.size.height += 100.0;
self.concluidoView.frame = frame;
}
completion:^(BOOL finished){
// complete
}];
The problem is that my UITableViewController is not scrolling enough to the new size, since the UIView is the last item in my tableView.
I've tried a few things to solve this problem, including manually trying to resize tableview to increase it's height to the same value of the UIViewincrease. I used the following code:
CGRect tableFrame = self.tableview.frame;
tableFrame.size.height += 100;
self.tableView.frame = tableFrame;
[self.tableview layoutIfNeeded];
The scrolling capacity of my tableviewwas actually smaller after this code. I would like to resize tableviewand allow scrolling, either manually, since I know how much my subviewwill increase, or automatically.
If you change the UITableView's frame, all you'll do is make it extend off screen most likely. What you want to do is get the table view to recognize that the UIView is larger. I'm assuming this UIView is the tableFooterView of your UITableView. Try doing the following:
UIView *footerView = self.tableView.tableFooterView;
self.tableView.tableFooterView = nil;
self.tableView.tableFooterView = footerView;
That will force the UITableView to reexamine the size of the footer view. I've had to do this before when changing the size of a footer view before.
Related
I have a container view which is equally divided between two UIViews like this:
The portion in black is my UIView 1, which I am currently not using. My UIView 2 contains a UISegmented Bar and a UITableView.
The hierarchy of my views look something like this:
Now, my requirement is I want to resize my view2 to cover entire container view dynamically and view1 to go away based on some condition. Currently I am not worried about that condition, I just tried to resize my view2 using the following code inside - (void)viewDidLoad
CGRect newFrame = self.view2.frame;
newFrame.size.width = 200;
newFrame.size.height = 200;
newFrame.origin.x=0;
newFrame.origin.y=0;
[self.view2 setFrame:newFrame];
Here view2 is the outlet to my view2 in interface builder.
But, nothing is changing from the above code. I tried to find any other way but had no help. So please help me find out my mistake in my current technique or tell me how some other technique to do it.
Thanks in advance!
The code to change frame seems fine, just remove the view from superview, set the frame and add to superview again programmatically.
[self.view2 removeFromSuperview];
// set the frame
CGRect newFrame = self.view2.frame;
newFrame.size.width = 200;
newFrame.size.height = 200;
newFrame.origin.x=0;
newFrame.origin.y=0;
[self.view2 setFrame:newFrame];
//add self.view2 again wherever it was
[myView addSubView:self.view2];
Inside a UICollectionView's supplementary view (header), I have a multiline label that I want to truncate to 3 lines.
When the user taps anywhere on the header (supplementary) view, I want to switch the UILabel to 0 lines so all text displays, and grow the collectionView's supplementary view's height accordingly (preferably animated). Here's what happens after you tap the header:
Here's my code so far:
// MyHeaderReusableView.m
// my gesture recognizer's action
- (IBAction)onHeaderTap:(UITapGestureRecognizer *)sender
{
self.listIntro.numberOfLines = 0;
// force -layoutSubviews to run again
[self setNeedsLayout];
[self layoutIfNeeded];
}
- (void)layoutSubviews
{
[super layoutSubviews];
self.listTitle.preferredMaxLayoutWidth = self.listTitle.frame.size.width;
self.listIntro.preferredMaxLayoutWidth = self.listIntro.frame.size.width;
[self layoutIfNeeded];
CGFloat height = [self systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
self.frame = ({
CGRect headerFrame = self.frame;
headerFrame.size.height = height;
headerFrame;
});
NSLog(#"height: %#", #(height));
}
When I log height at the end of layoutSubviews, its value is 149 while the label is truncated and numberOfLines is set to 3. After tapping the headerView, setting numberOfLines to 0, and forcing a layout pass, height then gets recorded as 163.5. Great!
The only problem is that the entire headerView doesn't grow, and the cells don't get pushed down.
How can I dynamically change the height of my collectionView's supplementary view (preferably animated)?
I'm aware of UICollectionViewFlowLayout's headerReferenceSize and collectionView:layout:referenceSizeForHeaderInSection: but not quite sure how I'd use them in this situation.
I got something working, but I'll admit, it feels kludgy. I feel like this could be accomplished with the standard CollectionView (and associated elements) API + hooking into standard layout/display invalidation, but I just couldn't get it working.
The only thing that would resize my headerView was setting my collection view's flow layout's headerReferenceSize. Unfortunately, I can't access my collection view or it's flow layout from my instance of UICollectionReusableView, so I had to create a delegate method to pass the correct height back.
Here's what I have now:
// in MyHeaderReusableView.m
//
// my UITapGestureRecognizer's action
- (IBAction)onHeaderTap:(UITapGestureRecognizer *)sender
{
self.listIntro.numberOfLines = 0;
}
- (void)layoutSubviews
{
[super layoutSubviews];
self.listTitle.preferredMaxLayoutWidth = self.listTitle.frame.size.width;
self.listIntro.preferredMaxLayoutWidth = self.listIntro.frame.size.width;
CGFloat height = [self systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
self.frame = ({
CGRect headerFrame = self.frame;
headerFrame.size.height = height;
headerFrame;
});
if (self.resizeDelegate) {
[self.resizeDelegate wanderlistDetailHeaderDidResize:self.frame.size];
}
}
// in my viewController subclass which owns the UICollectionView:
- (void)wanderlistDetailHeaderDidResize:(CGSize)newSize
{
UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;
// this is the key line
flowLayout.headerReferenceSize = newSize;
// this doesn't look beautiful but it's the best i can do for now. I would love for just the bottom of the frame to animate down, but instead, all the contents in the header (the top labels) have a crossfade effect applied.
[UIView animateWithDuration:0.3 animations:^{
[self.collectionView layoutIfNeeded];
}];
}
Like I said, not the solution I was looking for, but a working solution nonetheless.
I ran into the same issue than you, so I was just wondering: did you ever get a solution without the crossfade effect that you mention in the code sample?. My approach was pretty much the same, so I get the same problem. One additional comment though: I managed to implement the solution without the need for delegation: What I did was from "MyHeaderReusableView.m" You can reference the UICollectionView (and therefore, the UICollectionViewLayout) by:
//from MyHeaderReusableView.m
if ([self.superview isKindOfClass:UICollectionView.class]) {
//get collectionView reference
UICollectionView * collectionView = (UICollectionView*)self.superview;
//layout
UICollectionViewFlowLayout * layout = (UICollectionViewFlowLayout *)collectionView.collectionViewLayout;
//... perform the header size change
}
Consider the following UIView "MainView":
The view includes a Container View which in turn houses a UITableView controller. The container view's y coordinate starts just beneath the gradient bar. The UITableView includes the section footer at very bottom with the 'STU' label and 'chart' button.
When the UIView loads, and up-to-and-until any interaction with the tableView, MainView's dimensions are:
Frame: 0.000000x, 0.000000y, 568.000000w, 268.000000h
I have a delegate protocol set up such that tapping the chart button in the tableView will create a new view in MainView for a shadow effect via a method performing:
CGRect newFrame = self.view.frame; // self = MainView
newFrame.size.width = 100;
newFrame.size.height = 50;
UIView *backgroundShadowView = [[UIView alloc] initWithFrame:newFrame];
backgroundShadowView.backgroundColor = [UIColor blackColor];
// Do Animation
The important part above is the 'newFrame' CGRect. For some reason after interacting with the table view by tapping the chart button, or even scrolling or tapping a row, self.view.frame suddenly has the following dimensions:
Frame: 0.000000x, 52.000000y, 568.000000w, 268.000000h
And so the shadow view appears as follows, with a y origin much farther down than where it would be expected to start, just above the gradient bar.
I've adjusted the width and height of the "shadowview" for this question; normally it would be 568x268, but would extend 52 units off screen on the bottom because of this issue.
52 units is exactly the height of the statusbar (20) + navigationbar_in_landscape (32).
Of course I could manually adjust the frame dimensions, but I do not want to. I want to know why the view's frame is changing unexpectedly.
For the life of me, I cannot figure out why the view becomes suddenly offset. Any help is appreciated!!
Two comments.
(1)
This code was probably always wrong:
CGRect newFrame = self.view.frame; // self = MainView
newFrame.size.width = 100;
newFrame.size.height = 50;
UIView *backgroundShadowView = [[UIView alloc] initWithFrame:newFrame];
You surely want to define backgroundShadowView's frame in terms of self.view's bounds, not its frame as you are doing in the first line here.
(2)
The change in self.view.frame is probably illusory. You are probably checking this initially in viewDidLoad. But that is too soon; the view has not yet been added to the interface, and so it has not yet been resized to fit the surroundings.
I'm having a problem placing a newly created view over a UICollectionView cell (and grow it to fill the screen). It works great when the collection view is scrolled to the top, however as soon as I scroll, I'm unable to properly place the new view, since the float values I'm gathering are below the window height.
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
UIView *picturesView = [[UIView alloc] init];
picturesView.frame = CGRectMake(CGRectGetMidX([[collectionView cellForItemAtIndexPath:indexPath] bounds]), CGRectGetMidY([[collectionView cellForItemAtIndexPath:indexPath] bounds]), 100, 100);
[self.view addSubview:picturesView];
[UIView animateWithDuration:0.3 animations:^{
picturesView.frame = CGRectMake(0, 0, windowWidth, windowHeight);
picturesView.alpha = 1.0;
}];
}
The issue here is that the values in the x and y paramaters of CGRectMake are larger than the available window area after scrolling, and the view appears below the scroll clipping (the newly create view appears to slide up from the bottom of the screen, which is not the desired animation).
Logic would leave me to calculate where these should be placed by positionY = ((totalScrollHeight - topScrollAreaClipped) + screenPositionOfCell). The only variable I can't seem to get is the height of the UICollectionView being clipped by the scroll.
Unless there's a better way to do this.
You need to compensate for the scroll location of the UICollectionView. Check the scrollOffsett property, and subtract that value from your x/y location to give the correct frame for your UIView.
I have been struggling for a long time with a problem with a UIScrollView.
I have a UIScrollVIew that contains a UITextView as a subview. When I select the text view a keyboard pops up. I want to resize the text view to fit exactly in the available space, and also scroll the scroll view so that the text view is positioned exactly in the visible space (not hidden by the keyboard).
When the keyboard appears I call a method that calculates the appropriate size for the text view and then performs the following code:
[UIView animateWithDuration:0.3
animations:^
{
self.textView.frame = frame;
}
];
[self.scrollView setContentOffset:CGPointMake(0,frame.origin.y) animated:YES];
(here frame is the appropriate frame for the text view).
Unfortunately the scroll view does not always scroll to the correct position, especially when it is already at a non zero vertical content offset when I select the text view. I know that the content offset that I'm setting it to is correct.
After a lot of testing I finally realized that what was happening was that after the animation completed, the scroll view was automatically scrolling again.
This code does work:
UIView animateWithDuration:0.3
animations:^
{
self.textView.frame = frame;
}
completion:^(BOOL finished) {
[self.scrollView setContentOffset:CGPointMake(0, frame.origin.y) animated:YES];
}
];
but it looks strange because the scroll view scrolls to the wrong position, then to the right one.
Does anyone know how I can prevent the scroll view from changing it's content offset when the text view frame finishes its animation?
I am testing using iOS 5.0.
Here is a solution that I found that works. I'm still don't completely understand what's happening, possible it has something to do with the way my springs and struts are set. Basically I am shrinking the scroll view content size by the same amount that the text view shrinks.
- (void)keyboardDidShow:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
// Get the height of the keyboard
CGRect kbRect = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue];
kbRect = [self.view convertRect:kbRect fromView:nil];
CGSize kbSize = kbRect.size;
// Adjust the height of the text view to fit in the visible view
CGRect frame = self.textView.frame;
int visibleHeight = self.view.frame.size.height;
visibleHeight -= kbSize.height;
frame.size.height = visibleHeight;
// Get the new scroll view content size
CGSize contentSize = self.scrollView.contentSize;
contentSize.height = contentSize.height - self.textView.frame.size.height + frame.size.height;
[UIView animateWithDuration:0.1
animations:^
{
self.textView.frame = frame;
// Note that the scroll view content size needs to be reset, or the scroll view
// may automatically scroll to a new position after the animation is complete.
self.scrollView.contentSize = contentSize;
[self.scrollView setContentOffset:CGPointMake(0, frame.origin.y) animated:YES];
}
];
// Turn off scrolling in scroll view
self.scrollView.scrollEnabled = NO;
}
// Called when the UIKeyboardWillHideNotification is sent
- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
// Update the view layout
[UIView animateWithDuration:0.3 animations:^
{
self.scrollView.contentOffset = CGPointZero;
[self updateViewLayout];
}
];
// Turn on scrolling in the scroll view
self.scrollView.scrollEnabled = YES;
}
([self updateViewLayout] is a method that returns the text view to the correct height, and resets the scroll view content size, as well as making sure all the other subviews are properly positioned).
Do not adjust the frame of the text view. All you need to do is scroll the scrollview. If you scroll the scrollview animated, then the textView will slide up animated. Just do the following:
[self.scrollView setContentOffset:CGPointMake(0, frame.origin.y) animated:YES];
If you need the scroll view to scroll at a specific rate then manually adjust the content offset in an animation block.(I haven't tested this, you may want to adjust the animation flag, or step through the content offset one pixel at a time if this doesn't work)
[UIView animateWithDuration:0.3f
animations:^{
[self.scrollView setContentOffset:CGPointMake(0, frame.origin.y) animated:YES];
} ];
I believe I also had something similar. I wanted my horizontal UIScrollView to go back to it's original offset (0,0), and every time I animated back to it, it would be just a couple pixels off, but as soon as someone touched the scrollview, it would continue and finish the animation (almost like lag in a video game).
Due to that, I tried putting it in GCD as the main queue and it worked perfectly fine after that. Here's the code:
- (void)setPageZero{
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:1.0f
delay:0 options:UIViewAnimationOptionCurveEaseInOut
animations:^{
[self.scrollView setContentOffset:CGPointMake(0, self.scrollView.frame.origin.y) animated:YES];
}
completion:^(BOOL finished) {}];
});
}
What that basically does, is that it puts the animation into the main queue (kind of like a superhighway in the CPU/GPU) and it prioritizes it over other things!
Hope this helps future readers!
I'm having a similar problem. Basically I have a scroll view with three pages each containing a custom UIView class. The custom UIView class contains an image and a number of overlay views containing information about the image. The overlay views contain a number of UITextView fields.
I attach the custom views to the scroll view and then I load the image & overlays using block operations. I swipe to the right by deleting the first view, repositioning the next two views, and adding an new view to the right.
If I create the UITextField objects and disable scrolling as follows:
UITextView* view = [[UITextView alloc]
initWithFrame:CGRectMake(position.x,position.y,width,height)];
view.editable = NO;
view.scrollEnabled = NO;
Then the scroll view gets repositioned between pages when I return from the block code. Note that I don't reposition the scroll view and on exit from the block, the scroll view is in the correct position.
However, if I comment out the code to disable scrolling
UITextView* view = [[UITextView alloc]
initWithFrame:CGRectMake(position.x,position.y,width,height)];
view.editable = NO;
// view.scrollEnabled = NO;
this repositioning does not occur and the scrolling works as expected.
Complete hack: May improve the reset issue:
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
[self setContentOffset:self.contentOffset animated:NO];
}