How should I implement a custom grid on an iPad? - ios

I'm designing an iPad app that will have a custom grid in it. The grid will display simple geometric shapes, in different colors, that contain a single character. These will update frequently based on user actions.
The grid will be larger than the screen size, in both directions, so will need to scroll/pan.
I also need the top row and first column to be "frozen" - so the top row remains at the top, but the content within it scrolls horizontally with the rest of the grid, and the first column remains at the left, but the content scrolls veritical with the rest of the grid.
The contents of the first column could be wide, so I'll need to allow the user to resize it.
I'm struggling with the best way to design this. I'm thinking that it might be easiest just to have it as a single custom view when I manage all the drawing and interaction manually. But I can't help feeling that I'm missing some easier way of doing it. Maybe there's even a suitable third party component that would be a better starting point.
What would be the best way of designing this component of the app?

I'd have 3 UIScrollViews that you can set the frames for apropriately so that they make the first column and first row the size you wish - the main grid contents can be the bottom right UIScrollView.
The controller class can be the UIScrollViewDelegate for all of the views. Set the contentsize of the 1st column UIScrolView to be the same width as the width of its frame, but height the right size for the content, the 1st row UIScrollView should have the same height as its frame, but the right width for the content, and the bottom left UIScrollView will have it's content size set as the size of teh grid contents.
The - (void) scrollViewDidScroll:(UIScrollView*)scrollView method is called when scrolling any one of them so check which one has scrolled and scroll teh other two as appropriate.
i.e.
//Declared and positioned somewhere earlier, like in the .h
UIScrollView* firstCol;
UIScrollView* firstRow;
UIScrollView* mainGrid;
- (void) scrollViewDidScroll:(UIScrollView*)scrollView {
if (scrollView == firstCol) {
CGPoint offset = mainGrid.contentOffset;
offset.y = firstCol.contentOffset.y
[mainGrid setContentOffset:offset];
}
else if (scrollView == firstRow) {
CGPoint offset = mainGrid.contentOffset;
offset.x = firstRow.contentOffset.x
[mainGrid setContentOffset:offset];
}
else if (scrollView == mainGrid) {
CGPoint offset = firstRow.contentOffset;
offset.x = mainGrid.contentOffset.x;
[firstRow setContentOffset:offset];
offset = firstCol.contentOffset;
offset.y = mainGrid.contentOffset.y;
[firstCol setContentOffset:offset];
}
}

UICollectionView does a pretty good job for it. But if you need a data grid, then look at this one. It is in alfa stage, but it will be quite a powerful in month or two, I would say. Any suggestions are welcome and it's free, of course.

Related

Nested Scroll view vs Table View or something else for this requirement

I have a requirement in which I need to have the following functionality -
1)I have a custom segmented control. In order to implement paging to the segmented control I have used horizontal scroll view. Each page has its own vertical scroll view.
Requirement
1)The image should hide as user scrolls up in the respective pages and should show down when user scrolls down in respective pages but keeping the custom segment always at the top of the screen when image is hidden irrespective of the individual page selection-
What I have tried so far -
1st Method
I tried putting the image as header of a table view.
Created a single section with one cell & gave the section header as the custom segment. And in the cell I placed the horizontal scroll view with the cell's height adjusted to cover all portion left out of the superview but it didn't work out as when I scroll the vertical scrolling of individual pages it was not in sync with the table view.
2nd Method
I tried setting the segment initially with a fixed distance from the top & I increased & decreased the constraint inside scrollViewDidScroll(). But it too didn't work as when the user scrolled rapidly ,the changing of constraint value didn't follow correctly.
So is there any other way to achieve the same ?
Please suggest as I can't make out what to do?
You add a tableView and your UIImage on top of it inside a scrollView. The tableView must have the same height & width than your scrollView. Then you disable the pan gesture of the scrollView :
self.scrollView.panGesture.active = false
Then you have to implement a custom scroll in scrollViewDidScroll' of yourtableView`'s delegate:
func scrollViewDidScroll(scrollView: UIScrollView) {
if self.scrollView.contentOffset.y <= 100 {
self.scrollView.contentOffset.y += scrollView.contentOffset.y
self.tableView.contentOffset.y = 0
} else {
// let the tableView scroll normally
}
}
Or, you can have a try with https://github.com/bryankeller/BLKFlexibleHeightBar ;)
It's a great component that can handle many type on animation in the header based on the position of a scrollView.

How can I detect when a UITableView has been scroll past the very bottom/top?

I've got a view that has 3 tableView's. One is the "Main Table View", and then I have an 'Answers Table View' and 'Percentage Table View'.
When the screens loads, the Main Table View occupies the top 95% of the screen. The bottom of the screen is a UIView containing 2 buttons. "Answers" and "Percentage".
The way it works, is if I click "Percentage" it changes the height of the Main Table View to 0, and gives that height to the Answers Table View. This animates the "Answers/Percentage" View to the top, and reveals either the Answers or Percentage TableView below it.
Here's an example:
As you can see, I click on "Percentage" which animates it up. If you click on "Percentage" again it animates it back down.
However, what I want to do is if the "Answers/Percentage" view is at the bottom of the View, and the user scrolls the Main Feed UP reaches the very end of the tableView's contents (not just the end, but the end and a little bit more), I want to animate it up like in the .gif.
Similarly, if the "Answers/Percentage" is at the top, and the user scrolls the lower "Answers Table View" down past a certain point where there is no more data above, it will completely animate.
Also, I do not ever want the "Answers/Percentage" view to be in the middle, and showing a tableview both top and bottom. All one, or the other, but not a bit of both. Which I have right now.
What I need to know is... how can I detect if the user has scrolled past the very top or very bottom of the table view +30 pixels for example, to initiate my animation?
You can use the contentOffset property of the table view.
if(tableView.contentOffset.y >= (tableView.contentSize.height - tableView.frame.size.height)) {
// Start the animation
}
I haven't tested this, but let me know if it works.
Making another answer based on sublimepremise's approach because it's easier to format and post code that way. The base idea is to check the table view's .contentOffset.y, e.g. by implementing scrollViewDidScroll in its delegate, and triggering your animations accordingly.
It may have a bit of a QnD feel to it, but if you need to also abort the user's dragging action when triggering the animation, an easy way to do so would be by "resetting" the table view's gesture recognizer. In code that could look something like this:
static const CGFloat kChrisTableViewAnimationThreshold = 30.0f;
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView == self.topTableView) {
if([self scrolledPastBottomThresholdInTableView:self.topTableView]) {
// Start the animation
// ...
// Toggling the table view's pangGestureRecognizer off and on cancels the gesture
self.topTableView.panGestureRecognizer.enabled = NO;
self.topTableView.panGestureRecognizer.enabled = YES;
}
}
}
- (BOOL)scrolledPastBottomThresholdInTableView:(UITableView *)tableView {
return (tableView.contentOffset.y - kChrisTableViewAnimationThreshold >= (tableView.contentSize.height - tableView.frame.size.height));
}
Note that this illustration obviously only covers recognizing when the top table view is scrolled past its bottom end, but adapting it for handling other scenarios should be pretty straightforward.

How can I cause UIScrollView to lock 2 out of 3 UIViews when scrolling?

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.

Mirror scrolling of UICollection

I have created a UICollection with a custom layout to allow for scrolling both vertical and horizontal. It is a grid of equal sections and items in each section (ie 10 x 10, 20 x 20, etc). I would like to be able to put two headers that remain in view, one along the top and one along the left side. I have not found a way to do this within the UICollection itself. So, I set up UICollection along the left and another along the top. However, as the user scrolls the grid left and right and/or up and down, I want these two collections to mirror those movements.
So, my question is: Is there a way to mirror the horizontal movement of the main UICollection to the top UICollection and then mirror the vertical movement of the main UICollection to the side UICollection?
Thanks!
UICollectionView is a subclass of UIScrollView. It sends its delegate all of the messages defined in the UIScrollViewDelegate protocol.
The message you want to respond to is scrollViewDidScroll:. When your main collection view sends this message, you want to respond to it by getting its contentOffset and applying the offset to your margin collection views as appropriate.
// Implement this in your main collection view's delegate.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
[self synchronizeCollectionViewContentOffsets];
}
- (void)synchronizeCollectionViewContentOffsets {
CGPoint offset = self.mainCollectionView.contentOffset;
self.leftMarginView.contentOffset = CGPointMake(0, offset.y);
self.topMarginView.contentOffset = CGPointMake(offset.x, 0);
}
So, I figured it out. Using self.mainCollectionView.contentOffset always returned (0,0), so tried to use the object name I actually assigned to it through the Storyboard and the view controller. This worked.
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
[self synchronizeCollectionViewContentOffsets];
}
-(void)synchronizeCollectionViewContentOffsets {
CGPoint offset = myCollectionView.contentOffset;
myLeftMargin.contentOffset = CGPointMake(0, offset.y);
myTopMargin.contentOffset = CGPointMake(offset.x, 0);
}
Where myCollectionView, myLeftMargin and myTopMargin are linked to the UICollectionViews through the Storyboard.

Add content to the top of a scrollview

I'm trying to recreate the built in messaging app's view. I need to add talk bubbles to the bottom as well as prepend them on the top when i click "load previous".
My main issue is that I don't know how to push the rest of the talk bubbles down when i load more to the top. It's been quite a struggle for me.
I'm working in a subclass of UIScrollView and i've added an "innerView" to that.
What i do is add labels(bubbles) to negative values on the top and positive values on the bottom. I store the last positions in "topLabelsPosition" and "bottomLabelsPosition"
Can anyone help with this? Here's my code
CGFloat whereToScroll = 0.0;
CGFloat topOfContent = self.topLabelsPosition.origin.y;
CGFloat bottomOfContent = self.labelsPosition.origin.y;
CGFloat fullHeight = fabs(bottomOfContent)+fabs(topOfContent);
[innerView setFrame:CGRectMake(0,topOfContent,self.frame.size.width, fullHeight)];
if(is_adding_to_top) {
whereToScroll = topOfContent;
} else {
whereToScroll = bottomOfContent;
}
[self setContentSize:contentSize];
CGPoint point = {0, whereToScroll};
[self setContentOffset:point];
My "innerView" does not get bigger on top, but on bottom - i can tell by the background color.
And my scrollview won't scroll to the -300.00 (topOfContent) like i want it to.
I'm open to rewriting whatever and I'm all ears if you'd be so kind as to help.
Thank you so much in advance!
If you look closely at the Messages.app it doesn't scroll when it loads new bubbles at the top. they just blink in. As opposed to new messages appearing in the bottom where it scrolls them in.
I believe they aren't adding them to the top so much as adding them to (0,0) then moving everything already in the scrollView down in the content view. So try not adding to -xxx y but rather 0+ y and adjusting the frames of everything already inside down by the same amount.
Hope this helps.

Resources