I have a view that contains a UIScrollView that is designed to scroll horizontally. (So the UIScrollView is wider than the Self.View width)
Inside the UIScrollView I have a dozen UILables that I let the user cycle through adding values programmatically, when the next UILable is programmatically selected, if it is out of bounds I change the UIScrollViews offset to make sure the UILabel appears just to the left of the right side of the view.
This is how I am currently checking the UILabel position and then adjusting the offset of the UILabel.
if (positionLabel.frame.origin.x > self.view.frame.size.width)
{
[axisContainerScrollView setContentOffset:CGPointMake(positionLabel.frame.origin.x+40 - self.view.frame.size.width, axisContainerScrollView.frame.origin.y)
animated:YES];
}
else if (positionLabel.frame.origin.x > self.view.frame.size.width)
{
axisContainerScrollView setContentOffset:CGPointMake(0.0, axisContainerScrollView.frame.origin.y)
animated:YES];
}
So positionLabel origin brings back the value of its position inside the UIScrollView then I change the offset of the axisContainerScrollView. The only problem with this is that if I scroll the view across and select a UILabel whose offset is already inside the view if send the label back across to the right..
I would like to adjust this if statement so that if the UILabel is inside the bounds of self.view then I don't want to change the offset.
You could convert the coordinate space of positionLabel.frame to be the same as self.view, then you can use CGRectContainsRect function to make the comparison you're wanting to do.
something like -
// convert label frame
CGRect comparisonFrame = [axisContainerScrollView convertRect:positionLabel.frame toView:self.view];
// check if label is contained in self.view
BOOL isContainedInView = CGRectContainsRect(self.view.frame, comparisonFrame);
Now you have a BOOL, you can place in any conditional, that you can use to check.
Have you looked at [UIScrollView scrollRectToVisible:animated:]? It may be a more elegant method to do what you want. I'm pretty sure it won't try to scroll if the rectangle is already visible.
Related
Searched a lot on stack overflow, but didn't find any solution that works for us.
What we have are a couple of Views and UILabels and UIButtons in UIScrollView as below format. On Click of button we are hiding couple of views and labels and trying to recalculate UIScollView height.
UIScrollView
-->UIView
-->UIView2
-->UIView3
-->UILabel1
-->UILabel2 ( Please note these labels are not inside UIView, they are directly added to scrollview. Is this correct approach or they should be added inside a UIView?)
-->UITextField (Directly added to scrollview)
-->UITextField1 (Directly added to scrollview)
-->UIButton ( On Click of Button above textfields and labels are hidden or shown based on business logic)
When we try to reset size of UIScrollview on UIButton click it doesn't calculate height correctly. Tried below solution shared by lot of answers in stack overflow
Is the problem with labels and buttons added directly to UIScrollView?
Is there better way to set height of UIScrollview correctly?
We don't want to use AutoConstraint
CGRect contentRect = CGRectZero;for (UIView *view in uiScrollViewObj.subviews) {
contentRect = CGRectUnion(contentRect, view.frame);
}
uiScrollViewObj.contentSize = contentRect.size;
No it's not, in fact UILabels and UIButtons are UIViews.
I think you are confusing two concepts. The UIScrollView height is the height of the viewport (the visible part of the scrollview). What you want to change is the contentSize height that is the height of the whole content which you want to scroll through (I don't know if I have explain myself clear).
If the structure is the way you said it, so the UIButton is the closest element to the button, you should move its frame and place it upper, otherwise the contentSize is always gonna be the same.
Besides that, your code is iterating through all the views of the scrollview and those are not only the ones you added but also the ones that Apple inserted.
The best way to calculate the contentsize dynamically is to sum scrollview subviews y position and their height. Something like this should work:
CGFloat viewY, maxY=0;
for (UIView *view in uiScrollViewObj.subviews) {
if (view.hidden) continue;
viewY = view.frame.origin.y + view.frame.size.height;
maxY = (viewY > maxY) ? viewY : maxY;
}
[uiScrollViewObj setContentSize:CGSizeMake(uiScrollViewObj.frame.size.width, maxY)];
I have a UITableViewController and I put a UIView right under the navigation item and above the actual table. The problem that I have is that the view scrolls with the tableview.
How would I get it to behave exactly like the nav bar, and have the items in the tableview scroll behind it.
Rather than having the view scroll, it should remain in its position and have everything go behind it. Sorry for reiterating, but I've found thats necessary sometimes.
The view you're placing above the cell in the storyboard becomes the table view's tableHeaderView.
You can make the header view appear fixed by resetting its frame.origin to the table view's bounds.origin every time the table view lays out its subviews:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
UIView *header = self.tableView.tableHeaderView;
CGRect frame = header.frame;
frame.origin = self.tableView.bounds.origin;
header.frame = frame;
}
Result:
Assuming you don't want the map view to move then you could set its user interaction to false.
Alternatively you could set the header of your tableView (if you only have one section) to the map view.
I have an iPad app (XCode 4.6.3, iOS 6.2, ARC and Storyboards) which has the following structure on the bottom half of one of the scenes (all of the grids are UIViews, as is SubViewData).
The purpose is to have something that looks like a spreadsheet; I need to be able to scroll horizontally and the Left Grid will stay locked and if I scroll vertically, the Top Grid will stay locked.
This what it looks like now, without the scrolling (there is more to the right and also down):
UPDATE: This is the code that defines the UIViews:
I have looked in SO and Google and found no examples of this. Can someone please tell me what I need to change to get this code to work properly, or give me some good docs where I can get detailed information on contentOffset? (I have already read the UIScrollView, and it's no help!)
SOLVED - it's now working... this is how I got it to work, with the help of Fogmeister:
created a separate top row and left row UIView to hold the grid hours and staff names
embedded those new UIViews in UIScrollViews (Editor -> Embedd in scroll view)
followed the instructions from Fogmeister with regard to the code to make it happen.
This is the new structure:
And this is the code to make it happen:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGPoint mainOffset = [scrollView contentOffset];
NSLog(#"\n\nmainOffset.x: %f\nmainOffset.y: %f", mainOffset.x, mainOffset.y);
// set the horizontal offset of the main view onto the column headers
[self.topGridSV setContentOffset:CGPointMake(mainOffset.x, 0)];
if(mainOffset.x < 0) {
[self.topGridSV setContentOffset:CGPointMake(0, 0)];
[self.schedScrollView setContentOffset:CGPointMake(0, 0)];
}
// set the vertical offset onto the row headers
[self.leftGridSV setContentOffset:CGPointMake(0, mainOffset.y)];
if(mainOffset.y < 0) {
[self.leftGridSV setContentOffset:CGPointMake(0, 0)];
[self.schedScrollView setContentOffset:CGPointMake(0, 0)];
}
OK, the way I'd do this is to move the "header" row and column into their own scroll view.
So you'll have a scrollview in the middle with the actual cells in.
Then you'll have a scroll view along the top that ONLY CONTAINS the column headers.
Then have a scroll view down the left that ONLY CONTAINS the row headers.
Now, make the "owning" view controller the delegate of the "main" scrollView with the cells in.
So you'll have...
UIScrollView *cellScrollView;
UIScrollView *columnHeaderScrollView; // along the top
UIScrollView *rowHeaderScrollView; // down the left
You will have to set the content accordingly. Obviously, you don't want the headers in the cellScrollView. etc...
Now, in the delegate method...
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
// scrollView here should always be == self.cellScrollView
// as this is where the delegate method is triggered from.
CGPoint *mainOffset = [scrollView contentOffset];
// set the horizontal offset of the main view onto the column headers
[self.columnHeaderScrollView setContentOffset:CGPointMake(mainOffset.x, 0)];
// set the vertical offset onto the row headers
[self.rowHeaderScrollView setContentOffset:CGPointMake(0, mainOffset.y)];
}
Something like this should easily get the effect you're after.
With one scroll view
You will have three sub views of the scroll view and references to these...
cellView
leftView
topView
These are all subclasses of UIView.
Initially you will have a layout of something like...
leftView frame == [0, 50, 80, some long height]
topView frame == [80, 0, some long width, 50]
cellView frame == [80, 50, some long width, some long height]
i.e. the cell view will be indented by the height of the top view and the width of the left view. (I hope this makes sense).
So in your scrollViewDidScroll...
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
// we will transform the position of the top view and left view using the offset.
// I chose a transform as it always acts from the original position.
// changing the view's frame will make it difficult to set it back again.
CGPoint offset = [scrollView contentOffset];
// move the left view to the left and right depending on the offset
leftView.transform = CGAffineTransformMakeTranslation(offset.x, 0);
// up and down is taken care of by the scroll view correctly.
// move the top view up and down depending on the offset
topView.transform = CGAffineTransformMakeTranslation(0, offset.y);
// left and right is taken care of by the scroll view correctly.
}
I chose to use transform as it make the calculations easier. Instead of trying to calculate the difference in position required each time, you just set the transform amount to the offset and it works.
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 have a UIView that has two child elements: a UIScrollView on the upper half (which contains two UILabels), and a UITableView at the bottom. This is basically a dictionary and the purpose of the scroll view is to display the word and definition, and the table view for displaying the related words. Not all words in my dictionary have a related words array associated to them, so I hide the UITableView when that array is empty.
However, I can't get the UIScrollView to fill the entire parent view when the UITableView is hidden. Here's what I've tried so far:
- (void)updateUIWithWord:(NSString *)theWord
andDefinition:(NSString *)theDefinition
andRelatedWordsArray:(NSArray *)theRelatedWordsArray {
self.navigationItem.title = theWord;
self.word.text = theWord;
self.definition.text = theDefinition;
self.relatedWordsArray = theRelatedWordsArray;
if (![relatedWordsArray count]) {
relatedWordsTableView.hidden = YES;
// set the UITableView's width and height to 0 just to be sure
// I feel this isn't needed though
CGRect relatedWordsTableViewFrame;
relatedWordsTableViewFrame.size = CGSizeMake(0, 0);
relatedWordsTableView.frame = relatedWordsTableViewFrame;
// then make the scroll view occupy the remaining height;
// that is, the self.view's actual height
CGRect scrollViewFrame;
scrollViewFrame.origin = CGPointMake(0, 0);
scrollViewFrame.size = CGSizeMake(self.view.frame.size.width, self.view.frame.size.height);
scrollView.frame = scrollViewFrame;
}
}
Simply put, this doesn't work. For any word that has no related words and a very long definition, the scroll view simply occupies the same amount of height even with the table view gone. Help?
ADD: I tried fixing the constraints in the UIScrollView to make it relative to the top of the UITableView instead of having a fixed height, but that doesn't seem possible.
You have an "if" and an "else". Only one of those is going to execute. So when the "if" part runs and relatedWordsTableView.hidden is set to YES, the table view is hidden but nothing else happens. The "else" part isn't running so nothing is happening.
Approached the problem in a different way. I made the UIScrollView occupy the whole screen and put the UITableView inside it, below my two labels, with scrollling disabled. Now I can just hide and show it.