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.
Related
I have a UICollectionView with just 1 section and many cells in that section. I've enabled drag and drop behavior (with a long press gesture recognizer, because the UICollectionView sits in a generic UIViewController).
I'd like the drag and drop behavior to detect when the dragged cell is over the top of another cell. Google search didn't yield anything helpful, but Stackoverflow has this solution. While I can try to implement that by writing something similar in the UIViewController class to check if the dragged cell's Rect intersect with every other cell, I'm wondering if there's a more efficient way to accomplish this goal?
Off the top of my head, I think you could just grab the min and max X or Y coordinates (depending on whether the collection view is horizontal or vertical) of the dragged view, and then do a binary search through the views in the collection until you find one that intersects. This way, you wouldn't have to test all the views.
EDIT: Adding an example to clarify what I mean. Suppose we have a collection of views that are arranged left-to-right:
0 1 2 3 4 5 6 7 8 9 10
We start by getting the min and max X values of our dragged view. We'll call those "minX" and "maxX". We start by looking at the view in the middle of our list, view 5. If maxX < view 5's min X, then the view we're looking for has to be one of 0...4. If minX > view 5's min X, then we're looking for something in 6...10. Otherwise view 5 is intersecting the dragged view in the horizontal dimension.
After testing view 5, we test either view 2 or view 8, depending on whether the dragged view is to the left or right of view 5. We continue via this method until we happen upon an overlapping view, and each time we test a view we eliminate half of the views from consideration, so it will perform better than having to test every view in the collection.
Once you find one view that overlaps the dragged view, you'll want to check the adjacent views to see if they also overlap. In the case where the dragged view overlaps with several other views, you'll have to choose the one that overlaps the greatest area.
I need to create a time bar like the image attached.The blue lines are the dates indicating some actions.The big Red Arrow mark is used to slide for selecting any of the blue line. At background the bar is divided into years.The small red circles are to indicate the years.
Any idea ,how do i start?
Thanks
If the blue bars don't move a lot, and you don't need to resize this view very often (which is usually the case on an iPhone), then I'd recommend subclassing UIControl (or UIView) and implement it yourself.
Have an NSArray property storing NSDate objects and methods like addDate:, removeDate:, removeDateAtIndex: to change it's content from an other object (like your view controller). In these methods you add or remove the passed date and call [self setNeedsDisplay]; to redraw the lines.
You will need a few methods to calculate the position (in pixels) of a date on the slider and to calculate the date at a specific coordinate. This should be easy to to (basic linear interpolation).
And assuming that the red slider is only able to point to blue lines (but not between them) add a NSDate pointer variable to the currently selected date. In the
In the drawRect: method, you need to draw all the lines using CoreGraphics functions. Look up the documentation if you need help with this. Apple has some great sample apps too.
To show the red slider, you could either add a custom UIImageView, with a UIPanGestureRecognizer and make sure it only moves vertically in the gesture handler. Or it might be possible to add a UISlider without track images (I'm not 100% sure about that). In either case, you need to adjust the slider's position once the user let's go and make it jump to the nearest blue line.
A rough idea,
1. Get the length of the entire TimeBar.Let it be t.
2. Divide by x=numberofYears*365.let it be t_eachday_pos=t/x
3. get the position of each day,calculate its offset as daycount*t_eachday_pos;
4. Add your blueLine as a customButton.
5. To do some action, pass the x daynumber(eg. day 245 of year 2 etc.)
Try this, I have used the same thing in one of my app, and it is working
I'd like to use a UItableView to show a day Calendar. Each row corresponds to one hour. I need to show the hour between 2 cell of my tableview.
Like this :
(source: free.fr)
And this is my UITableViewCell :
(source: free.fr)
In the first screenshot, it works perfectly but if I scroll down then scroll up, my time label is cut like this :
(source: free.fr)
Have you any tips to figure out this problem using a tableView ?
The way you lay out your cell now is fragile, because the order of painting the cells on screen matters a lot. Try moving the content up so that your buttons are flush with the top of the cell, and the time label fits into the cell entirely. Add a thin header view to your table to make the top cell appear normal. Keeping the content of a cell entirely within its bounds should help you maintain reasonable scrolling speeds.
EDIT : You could also put a second clipped label at the top of your cell, and make its content identical to that of the label in the prior row. You would need to take special care to hide that label in the top row, but otherwise this should make your table immune to changes in the rendering order of its cells.
Make the background color of your cell clear. As you scroll up the z ordering of your cells get reversed and the lower cells overlap the higher ones causing this clipping.
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
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. :)