Fast, 2 Dimensional Table View - iphone

I'm implementing a view for displaying tabular information with 2 axis, but I'm starting to run into performance issues with rendering all the cells.
The view looks something like this:
+------------+-------------+------------+------------+------------+
| | 12.00am | 1.00am | 2.00am | 3.00am |
+------------+---------+---+---+--------+--------+---+-+---+------+
| Category 1 | x | x | x | x | x | |
+------------+---------+-------+-----+-----------+-----+---+------+
| Category 2 | | x | x |
+------------+---------+-------+----+--+--------------------------+
| Category 3 | | x | x | x |
+------------+--+---------------+-------+-------------------------+
Each x marks the center of a cell, or a piece of data in a category that's placed on the timeline. The cell will contain a single text label. When the cell is pressed, I need to know that it was pressed. The x axis will contain 24 markers, and there will be anywhere between 5 & 30 rows of data.
At the moment, I've implemented it using 3 main UIScrollViews; one across the top, one down the side, and then a large one for all the cells. When the large scrollview is scrolled, it updates the contentOffsets of the top and side scrollviews. This seems to work quite well, and it's quite intuitive.
The large scrollView has a number of row views, aligned with the headers in the sidebar on the left. The cells are then slotted into their respective row views, and shifted across the x axis to align relative to the time markers. The data cells do not necessarily line up precisely with the hourly time increments of the x axis.
I've encountered two problems:
It seems to be really slow. In some of these tables, there can be up to 2500 cells. This implies I might need some kind of view re-use strategy.
When two cells are next to each other there's a 2px border, but when it's a single cell on its' own there's a 1px border (The CALayer of each row and cell view has a 1px border drawn).
Some possible naïve solutions to the first problem:
Create one large view for the data cells, throw it into the main UIScrollView and draw everything using Quartz (possibly in a background thread).
Implement a view-reuse strategy and deal with having blank space when the user scrolls around quickly.
Does anyone have any high-level input on the best way to approach this problem? I've not dealt with large amounts of data/views before, so any input would be greatly appreciated.

I created something called DTGridView (available as part of DTKit), intended for this exact purpose (it was for an electronic programming guide for an app over here). So it allows differing widths of cells like you've shown in your diagram.
DTGridView uses one single UIScrollView and calculates the needed positions. It also implements cell reuse (in the same way that UITableView does). In fact if you know how to use a table view, then a DTGridView is pretty similar; It uses a dataSource to collect the data and views, and provides delegate methods (using a gridDelegate property) to notify of certain events.
DTKit contains an example project, which should help you understand how to set a grid up.
Give me a shout if you need any help. I understand the kit as a whole needs some better documenting. :)

Related

Letting big shadows bleed between UITableView cells

I've created a UITableViewCell with a textfield in it, which has a large diffused shadow underneath it. I've also set it up so that these big shadows can blend under its surrounding cells.
NB. Shadow has been HEAVILY emphasised here to show the problem.
The problem is that I want the shadow from my EMAIL textfield to blend underneath the password cell. i.e. I want both textfields to have a white background, but underneath them both I have the shadow. Something like this:
The reason this is a problem is because the UITableView is being rendered with upper cells "above" the lower ones. So any shadow from the cell at row 1 will bleed on top of the cells in rows 2, 3, 4 etc.
I was wondering if perhaps there is a way to change this rendering, and instead reverse the rendering so that a cell in row 4 is actually ABOVE rows 3, 2 and 1.
Or perhaps there is a way of setting the Password textfield so that no shadows are rendered on top of it?
Any help, very much appreciated!
Thanks to #Amr for pointing me in the direction of the cell.layer.zPosition.
The higher the value this is, the "closer" to the viewer the layer sits, so as long as each row has a higher value, it will appear "above" the shadows from any cells around it.
Since I use multiple sections in my code, I used this line to assign each cell above the previous one.
cell.layer.zPosition = CGFloat(indexPath.section) * 1000 + CGFloat(indexPath.row)

Different cell appearance for a single collection view

I was asking here about the option to make a collectionView with multiple cells since our cells have a different appearance for different indexs.
The answer was not sufficient because other then just create multiple cells and pick one for every index, you have all sorts of problems such as :
How can you register multiple classes ? collectionView.register(FlowViewCell.self
Reusable cells system gets crazy because they are different
Every action you do on a cell must be identified with its class and it becomes a mess.
So, I am searching for another way to do so with a single cell.
What would be a good way to set different buttons in the same position of a cell, and show/enable a different button with different indexes ?
So for index 1 I have 2 circle buttons, and for index 2 I have one large wide button? if you just put them on the same spot and hide one you get a strange design.
The answer you linked is what you are looking for.
We use 4 different types of cell in our app and we don't have any problem with it.
You don't have to register multiple classes, you can all create them in your storyboard (inside the collectionview), the system will register the classes for you.
And then you call the cells like explain in the answer.
The hard part is having different sizes, we only have two (large cell and square cell, so two square cells fit on the same level), more than that would be tricky, especially different height.
But apart from that you can have different content on each cell without any problem.

UITableViewCell Panning to reveal options: Animated

So I have a very good implementation of panning on a tableview cell. I basically use panGestureRecognizer and move the cell off screen, and show another view below the tableViewCell. This is working fine. When I pan on the tableViewCell, I basically reveal 5 buttons underneath.
My goal is to animate the 5 buttons as the tableviewcell is moving. For example,
| 1 | 2 | 3 | 4 | 5|
If I am swiping, and moving the cell right and I am cross button 1, I will animate button popping out and become visible to the user.
If I am crossing 2, then 2 will animate popping out, and become visible to the user.
And when I am in button 5, all buttons are already popped and ready for user actions.
I am doing this with
//Scale Animation
CABasicAnimation *scaleAnim = [CABasicAnimation animationWithKeyPath:#"transform"];
scaleAnim.duration = 0.3;
scaleAnim.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
scaleAnim.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.1,0.1, 1.0)];
scaleAnim.removedOnCompletion = YES;
[view.layer addAnimation:scaleAnim forKey:nil];
**
My goal :
**
So when I am in the middle of a panning gesture, and I decide to close it ( move to the left), the buttons should pop-in, and become unavailable to the user.
Also, as I move the cell the button should gradually scale up, and become available to the user. So I will essentially need to pass the cell's origin and use some math to compute the scale for the buttons based on the button's origin and width (fairly straight-forward).
if (CGRectIntersectsRect(button.frame, self.movingCell.frame))
{
CGFloat xScale = cellTranslation - xOriginForButton;
[self popOutAnimate:button withScale:xScale/ (xWidthForButtons + xSpaceBetweenButtons)];
}
But the problem comes up when I move the cell really fast, the views don't get all the changes in cell's movement and they are stuck in a half animated state.
I have a feeling that I am not grasping the logic right. The whole implementation is similar to the Sparrow iphone mail app. When you pan on the cell, the buttons reveal with animation as the cell is moved. For those who have not used sparrow. You can see the panning gesture in 1:25 https://www.youtube.com/watch?v=XLX6XV0SbWI&t=1m25s
Is the logic correct? Am I missing something?
Any help is much appreciated.
After working on this using several different ways, I ended up with a solution for this, and I am typing it below.
Algorithm:
The principal to swiping or panning a tableViewCell is straight-forward.
The algorithm for popping out buttons underneath the cell is based on
the current location of the cell in comparison to the position of the
buttons. There are several ways to go about this, and the one used in
the following example is _NOT_THE_MOST_OPTIMAL_ one given the
complications that arise when we reorder the buttons. (i.e. if the
buttons are re-ordered, the algorithm will fail).
Lets look at the case of the best solution to this problem.
Perform a hit_test using (CGRectIntersects(rect1, rect2)to find out where the cell meets any of the
buttons.
==> This would be the most ideal solution as replacing or interchanging buttons will still work fine.
But We cannot use this approach as hit_test will fail when the cell is moved quickly.
This will leave the buttons in a very improper state.
Use the frame of the buttons directly based on the translation and
scale them up. ===> We cannot use the frame of the buttons for any
scenario because as we scale them, (x,y,size,width) vary drastically
Cache the button frames before animation and use them for scaling
them. ===> Caching them might work for certain situations, but we will
need to update them very regularly, and also make sure to cache them
after ORIENTATION_CHANGE. This will again be a pain as button frames
after orientation change also very drastically (Refer to pt 2).
Working:
So if we are going to be animating all the buttons underneath the cell, then you will have
only one regionOfAnimation. If you have a set of portion with static buttons, and a set of buttons that need to be
animated, then we have 2 regions - the one region with staticButtons, and the other regionOfAnimation.
These values for the region were computed based on the position of the animated region (as set in IB). We again split
the regionOfAnimation EVENLY based on the number of buttons present, and
as the cell moves across each split, we animate the buttons beneath it.
| Region of Static Buttons | Region of Animation |
---------------------------------------------------------
| Static Button | Static Button | 1 | 2 | 3 | 4 | 5|
---------------------------------------------------------
|
| ===> CELL MOVES ====>
|
---------------------------------------------------------
The advantages of this method:
Performance is better than hit_test (much needed break)
Buttons will always animate properly since we dont access their frames
for calculations
Dis-advantages:
The region of static buttons, and region of animation
must be calculated before hand for both orientations.
If the buttons are
interchanged, the algorithm needs to be updated to reflect the new changes.
you can find all your answers here https://github.com/spilliams/sparrowlike :) it is quite robustly commented and would be more modular.. try to edit this one.

Select one table cell and have multiple cells momentarily highlight

I have related items in adjacent table cells. When either of the related items is selected, before going to the detail view, I would like the momentary cell selection highlight to show both (or in some cases 3) cells highlighted to alert the user of the relationshp.
This a purely a "nice to have" cosmetic feature.
Any help appreciated.
In the method called when the user selects a row you can use:
[cell setSelected:YES];
One way to get the cell would be using tableView:cellForRowAtIndexPath method (correct me if I'm wrong).
* FOLLOW-UP TO Shaggy Frog *
I appreciate your comments.
For some reason, I can't add a comment to or edit my own original question so I am editing my follow-up.
May I have an opinion on the following. Since the 2 highlightd cells would always be adjacent and the data related, it makes sense that the detail view contain details for both cells. However your point is valid and I don't want to get rejected.
Instead of 2 cells, if for the "related cells" I were to double the height of those cells, draw a horizontal line half way between (to make the 2 "subcells" of 44 pixels in height looking just like 2 cells) and place the data in subviews so the result looks exactly the same as 2 cells but it would actually be 1 cell. Selection of one of my "subcells" would then highlight 2 "subcells" which is the entire cell - same effect but 1 cell.
Do you think that would pass?
And of course the biggest advantage is that I know how to do this versus the multi-cell highlighting which doesn't seem possible.

how to efficiently find a rect # some x,y point with iphone sdk

I am looking for an efficient way to handle the image/frame detection from touch methods. Let's say i am building a keyboard or similar to this. I have 'n' number of images placed on the UI. When someone touches an alphabet (which is an image), i can do the following to detect the corresponding letter
1) CGRectIntersectsRect(..,..) : if i use this, then i need to check each & every letter to find out what letter exists at that touch point (let's say 100,100). This becomes O(n). If i move my finger accross the screen, then i will get m points & all corresponding image detection becomes O(n*m) which is not good.
2) Other way is building a hash for each & every x,y position so that the look up will be simply O(1). But again this will be a memory constraint as i need to store 300*300 ( assuming i am using 300*300 screen size). if i reshuffle my letters, then everything needs to calculated again. So this is not good
In other words, i need some thing like , given a point (x,y), i need some way of finding which rectangle is covering that point efficiently.
Sorry for long post & any help would be grateful.
Thanks
If there are in a regular grid, then integer division by the grid size. Assuming you've a small, fixed screen size, a bucket array gives as similar gain ( a 2D grid, where each entry is a list of the rectangles which intersect that part of the grid ) is very fast if tuned correctly so the lists only have a few members. For unbounded or large spaces, KD trees can be used.
It's useful to have the rects you want as final targets set up as subviews as a larger UIView (or subclass) where you expect all these related hits to occur. For example, if you're building your own keyboard, you could add a bunch of UIButton objects as subviews and hit test those.
So, the easy and traditional way of hit testing a bunch of subviews is to simply have code triggered by someone hitting those buttons. For example, you could add the subviews as UIControl objects (which is a subclass of UIView that adds some useful methods for catching user touch events), and call addTarget:action:forControlEvents: to specify some method to be triggered when the user does something in the rect of that UIControl. For example, you can catch things like UIControlEventTouchDown or UIControlEventTouchDragEnter. You can read the complete list in the UIControl class reference.
Now, it sounds like you might be going for something even more customized. If you really want to start with a random (x,y) coordinate and know which rect it's in, you can also use the hitTest:withEvent: method of UIView. This method takes a point in a view, and finds the most detailed (lowest in the hierarchy) subview which contains that point.
If you want to use these subviews purely for hit testing and not for displaying, then you can set their background color to [UIColor clearColor], but don't hide them (i.e. set the hidden property to YES), disable user interaction with them (via the userInteractionEnabled BOOL property), or set the alpha below 0.1, since any of those things will cause the hitTest:withEvent: method to skip over that subview. But you can still use an invisible subview with this method call, as long as it meets these criteria.
Look at UIView's tag property. If your images are in UIViews or subviews of UIView, then you can set each tag and use the tag to look up in an array.
If not, then you can improve the speed by dividing your array of rectangles into sets that fit into larger rectangles. Test the outer rectangles first, then the inner rectangles. 25 rectangles would need only 10 tests worst case as 5 sets of 5.
Thanks Pete Kirkham & Tyler for your answers which are really helpful. Lets say i dont wanna use buttons as i am mainly displaying images as a small rectangles. To check for the rect # (x,y) , i can trigger that easily by making my grid as a square & finding
gridcolumn = Math.floor(pos.x / cellwidth); gridrow = Math.floor(pos.y / cellheight);
But my problem is with touchesMoved. Lets say i started # grid-1 & dragged till grid-9 (in a 3*3 matrix), in this case, i am assuming i will get 100-300 (x,y) positions, so everytime i need to run above formula to determine corresponding grid. This results in 300 calculations which might effect the performance.
So when i display an image as a rect, can i associate some id for that image? so that i can simply just save the ids in a list (from grid-1 to grid-9) so that i can avoid the above calculation.
Thanks for your help