UICollectionView with preview and paging enabled - cocoa-touch

I am trying to imitate what Apple has when showing the search result in the App Store. (reference: http://searchengineland.com/apple-app-search-shows-only-one-result-at-a-time-133818)
It shows like the detailed-application-info in a cards and it is paged. I am stuck at how to make the previous-and-next card shows when one active card in the middle and the scroll view's paging behaviour is still intact.
I have tried using the UICollectionView and set the clipSubviews to NO, hoping that it will show the previous page and the next page, but as soon as the cell goes off-screen, the cell gets hidden (removed from the view hierarchy) and not displayed. I think thats the flyweight pattern of the UICollectionView (the behavior of UICollectionView). Any ideas of what would be possible?
Cheers,
Rendy Pranata

The problem: UICollectionView as a subclass of UIScrollView essentially animates its bounds by a stride of bounds.size. Although this could mean that all you had to do is decrease the bounds while keeping the frame bigger, unfortunately UICollectionView will not render any cells outside its current bounds... destroying your preview effect.
The Solution:
Create a UICollectionView with paging set to NO and with the desired frame.
Create UICollectionViewCells that are smaller than the UICollectionView's frame/bounds. At this stage, a part of the next cell should show in the frame. This should be visible before implementing the other steps below.
Add a collectionView.contentInset.left and right (I assume your layout is horizontal) equal to the contentOffsetValue method (as shown below for simplicity) so as to align the first and last cells to the middle.
Create a UICollectionViewFlowLayout which overrides the method that gives the stopping point like so:
Like so:
-(CGFloat)contentOffsetValue
{
return self.collectionView.bounds.size.width * 0.5f - self.itemSize.width * 0.5f;
}
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
static float EscapeVelocity = 0.5f; // otherwise snap back to the middle
NSArray* layoutAttributesArray = [self layoutAttributesForElementsInRect:self.collectionView.bounds];
if(layoutAttributesArray.count == 0)
return proposedContentOffset;
CGFloat currentBoundsCenterX = self.collectionView.contentOffset.x + self.collectionView.bounds.size.width * 0.5f;
UICollectionViewLayoutAttributes* candidateNextLayoutAttributes = layoutAttributesArray.firstObject;
for (UICollectionViewLayoutAttributes* layoutAttributes in layoutAttributesArray)
{
if ((layoutAttributes.representedElementCategory != UICollectionElementCategoryCell) ||
(layoutAttributes == candidateNextLayoutAttributes)) // skip the first comparison
continue;
if(velocity.x > EscapeVelocity || velocity.x < -(EscapeVelocity))
{
if(velocity.x > EscapeVelocity && layoutAttributes.center.x > candidateNextLayoutAttributes.center.x)
{
candidateNextLayoutAttributes = layoutAttributes;
}
else if (velocity.x < -(EscapeVelocity) && layoutAttributes.center.x < candidateNextLayoutAttributes.center.x)
{
candidateNextLayoutAttributes = layoutAttributes;
}
}
else
{
if(fabsf(currentBoundsCenterX - layoutAttributes.center.x) < fabsf(currentBoundsCenterX - candidateNextLayoutAttributes.center.x))
{
candidateNextLayoutAttributes = layoutAttributes;
}
}
}
return CGPointMake(candidateNextLayoutAttributes.center.x - self.collectionView.bounds.size.width * 0.5f, proposedContentOffset.y);
}

I just put together a sample project which shows how you could do this. I created a container view which is 100 points wider than the 320 points for the screen. Then I put a UICollectionView into that container. This offsets everything by 50 points on both sides of the screen.
Then there is a content cell which simply has a background and a label so you can visually identify what is happening. On the left and right there are empty cells. In the viewDidLoad method the content inset is set to negative values on the left and right to make the empty cells now scroll into view. You can adjust the inset to your preference.
This mimics the behavior fairly closely. To get the label below, like in the example you can simply check the contentOffset value to determine which cell is in focus. To do that you'd use the UIScrollViewDelegate which is a part of UICollectionView.
https://github.com/brennanMKE/Interfaces/tree/master/ListView
You'll notice this sample project has 2 collection views. One is a normal horizontal flow layout while the other one which has larger cells is the one which mimics the example you mentioned.

Related

constrain proportions while resizing images

I implemented drag and drop of images and now i want to constrain proportions of images while resizing.
/**
* Variable: constrainChildrenOnResize
*
* Specifies if children should be constrained according to the <constrainChildren>
* switch if cells are resized (including via <foldCells>). Default is false for
* backwards compatiblity.
*/
mxGraph.prototype.constrainChildrenOnResize = false;
i set this to true but its not working :s
What API/property i need for this functionality..
constrainChildrenOnResize is responsible for positioning and size of the children of resized cell. It means that children should keep their position relatively to the parent-cell.
In your case I would suggest to extend mxVertexHandler using union method. In this example you can see how to implement min-width/min-height restrictions. Using this example you are able to write your own rules for constrain.
Here is my simple solution:
var vertexHandlerUnion = mxVertexHandler.prototype.union;
mxVertexHandler.prototype.union = function (bounds) {
var result = vertexHandlerUnion.apply(this, arguments);
var coff = bounds.width / bounds.height
result.width = result.height * coff;
return result;
};
So this function is called every time you move mouse during dragging the resizer.
bounds - object, always same and represent old geometry of the cell (before resizing)
result - object, represents new values, which are going to be applied. Between this line ad return statement you can place any code you need to modify result.
In my simple example I just get the initial relation between width and height of the cell (coff) and then set new width by multiplying coff and new height. It will work if you drag corner or top/bottom. In real project this logic should be slightly extended, or you should make visible only corner handlers.
By the way, this code will work for all resizable cells on your graph. If you want to apply it only to images or some other kind of cells - you can put condition and check the cell type before recalculating. You can get current cell or its state via this.state.cell or this.state inside of union function.
For example only for vertecies:
... ...
var result = vertexHandlerUnion.apply(this, arguments);
if (this.state.cell.isVertex()) {
//calculations here
}
return result;

Preventing SKSpriteNode from going off screen

I have a SKSpriteNode that moves with the accelerometer by using the following code:
-(void)processUserMotionForUpdate:(NSTimeInterval)currentTime {
SKSpriteNode* ship = (SKSpriteNode*)[self childNodeWithName:#"fishderp"];
CMAccelerometerData* data = self.motionManager.accelerometerData;
if (fabs(data.acceleration.y) > 0.2) {
[gameFish.physicsBody applyForce:CGVectorMake(0, data.acceleration.y)];
}
}
This works well however, the node (gamefish) moves off the screen. How can I prevent this and have it stay on the screen?
Try using an SKConstraint which was designed exactly for this purpose and introduced in iOS8:
Just add this to the setup method of the gameFish node. The game engine will apply the constraint after the physics has run. You won't have to worry about it. Cool huh?
// get the screensize
CGSize scr = self.scene.frame.size;
// setup a position constraint
SKConstraint *c = [SKConstraint
positionX:[SKRange rangeWithLowerLimit:0 upperLimit:scr.width]
Y:[SKRange rangeWithLowerLimit:0 upperLimit:scr.width]];
gameFish.constraints = #[c]; // can take an array of constraints
The code depends on whether you have added the gameFish node to self or to another node (something like a "worldNode"). If you have added it to self, look at the code below:
// get the screen height as you are only changing your node's y
float myHeight = self.view.frame.size.height;
// next check your node's y coordinate against the screen y range
// and adjust y if required
if(gameFish.position.y > myHeight) {
gameFish.position = CGPointMake(gameFish.position.x, myHeight);
}
For the bottom you can do a check of < 0 or whatever value you need.

NSWindow does not resize according to NSSize returned from windowWillResize:toSize:

I am writing a Cocoa application for OS X in the style of Mail.app where there are 3 panes. I'm achieving this by embedding one NSSplitView within another NSSplitView. I'm trying to mimic the functionality of Mail.app that automatically collapses the leftmost pane when the window is resized to be smaller than a specific width. I'm doing this using the following method of NSWindowDelegate
- (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)frameSize
My implementation of this method is below but in short what is happening is that as the window is resized to be smaller the left pane does collapse as I would like however after it collapses the window should be allowed to continue to get smaller but the kicker is that it doesn't. The NSSize that I am returning during this process is correct but the window does not resize to match this value. I've checked to ensure that the NSWindow does not have any other minimum size limitations. I'm stuck. Can anybody help me understand what is causing this problem?
My implementation:
- (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)frameSize
{
CGFloat minWidth;
if ([outerSplitView isSubviewCollapsed:leftPane])
{
minWidth = [outerSplitView dividerThickness] + MIDDLE_PANE_MINIMUM + [innerSplitView dividerThickness] + RIGHT_PANE_MINIMUM;
}
else
{
minWidth = LEFT_PANE_MINIMUM + [outerSplitView dividerThickness] + MIDDLE_PANE_MINIMUM + [innerSplitView dividerThickness] + RIGHT_PANE_MINIMUM;
}
if (![outerSplitView isSubviewCollapsed:leftPane])
{
// make sure the width is not smaller than current minimum width
// find the difference proposed size and current size
if (sender.frame.size.width - frameSize.width > LEFT_PANE_MINIMUM / 2.0 )
{
//Collapse the left pane. Don't modify framesize.
[outerSplitView setPosition:0.0 ofDividerAtIndex:0];
}
else if (frameSize.width < minWidth )
{
frameSize.width = minWidth;
}
}
else if (frameSize.width < minWidth)
{
frameSize.width = minWidth;
}
return frameSize;
}
I take it you're not using auto layout. You might consider doing so. I think you'd get this behavior for free once you set the appropriate minimum width constraints and holding priorities on your subviews. (The minimum widths can be from explicit constraints directly on the subviews or they can result from the subviews within the subviews and their intrinsic sizes, etc.)
Given that your split views are both vertical, is there a reason that you didn't just use a single split view with three subviews? Once you have a split view in your UI, you can just drag another view into it to increase the number of panes.
Your logic for deciding when to collapse the view doesn't make sense to me. You have if (sender.frame.size.width - frameSize.width > LEFT_PANE_MINIMUM / 2.0 ) which is determining if the user is attempting to drag by more than half the left pane's minimum. But how far the user has dragged isn't relevant. They may have started from a size much larger than the left pane's minimum.
I think you simply want to know if the proposed width is less than LEFT_PANE_MINIMUM / 2.0 + [outerSplitView dividerThickness] + MIDDLE_PANE_MINIMUM + [innerSplitView dividerThickness] + RIGHT_PANE_MINIMUM.
Also, since there's an absolute minimum width, you do always want to adjust the proposed frameSize. That is, even in the branch where you collapse the left pane you still want to enforce the minimum-width-when-the-left-pane-is-collapsed.
For example, try this logic:
- (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)frameSize
{
CGFloat minWidth = [outerSplitView dividerThickness] + MIDDLE_PANE_MINIMUM + [innerSplitView dividerThickness] + RIGHT_PANE_MINIMUM;
CGFloat minUncollapsedWidth = LEFT_PANE_MINIMUM + minWidth;
CFGloat forceCollapseWidth = LEFT_PANE_MINIMUM / 2.0 + minWidth;
if (![outerSplitView isSubviewCollapsed:leftPane])
{
if (frameSize.width < forceCollapseWidth)
{
//Collapse the left pane.
[outerSplitView setPosition:0.0 ofDividerAtIndex:0];
}
else if (frameSize.width < minUncollapsedWidth)
{
frameSize.width = minUncollapsedWidth;
}
}
if (frameSize.width < minWidth)
{
frameSize.width = minWidth;
}
return frameSize;
}
If that still doesn't let the window continue resizing smaller, then:
If you stop dragging and then start again, does that work? That
is, is the problem just with a continuous resize operation during
which the left pane collapsed, or is it that the window will never
allow itself to go smaller than what I've called
minUncollapsedWidth?
How have you determined that there's nothing else limiting the window's size? What did you check?

UIScrollView contentOffset CoreMotion Bug

I'm having a problem when I try to move the content of a UIScrollView with CoreMotion.
The thing is, I have a background with approximately 5000px, inside a container, which is inside my scrollView (is a landscape app); I already set the self.scrollView.bounces = FALSE;, my value for motion is attitude.pitch * 10.0; and when I move the content inside and it reaches the edge of the pointZero or the self.scrollView.contentSize.width it doesn't respect the bounds and keep moving in a white screen like it has no limit.
So, I set a verification (the code below), but when it reaches the pointZero it was supposed to stop but still give me a little white border. I set a NSLog and I saw that the contentOffset was still going till x =-14, like the bounce was active. The pitch value is controlling that because when the pitch value is zero the content stays at pointZero, when I raise the value of the pitch ir keep going till -14.
I think that is something wrong with my verification, if anyone can help I will be really grateful!!
self.accel = attitude.pitch *10.0;
//"pointZERO"----------------
if (self.gameArea.contentOffset.x <= self.gameArea.frame.origin.x) {
NSLog(#"%2.f",self.gameArea.contentOffset.x);
self.gameArea.contentOffset = CGPointZero;
} else {
self.gameArea.contentOffset = CGPointMake(self.gameArea.contentOffset.x + self.accel,0.0);
}
//"END OF SCREEN"------------------------
if (self.gameArea.contentOffset.x + self.gameArea.frame.size.width >= self.gameArea.contentSize.width) {
NSLog(#"%2.f",self.gameArea.contentOffset.x);
self.gameArea.contentOffset = CGPointMake(self.gameArea.contentSize.width - self.gameArea.frame.size.width ,0.0);
} else {
self.gameArea.contentOffset = CGPointMake(self.gameArea.contentOffset.x + self.accel,0.0);
}

How to add and delete a view in UIscrollview at runtime?

My project needs to add and delete a uiwebview to uiscrollview at runtime. Means, when we scroll it horizontally (left or right) when paging is enabled, then a new view get added to the uiscrollview and it traverse to it.
Is it possible to detect the left or right scrolling in uiscrollview?
Plz tell me the best possible solution, sample code or any tutorial.
Thanks in advance
In such cases, we should have paging enabled in our scroll view.
Lets say you have scroll view of size 320x480, and it is supposed to show 10 pages, where size of each page is 320x480, making content size of scroll view as 320*10 x 480.
The best way to determine the current page is by using the content offset value of the scroll view.
So, in the beginning, when scrollview is showing 1st page, its content offset would be x=0, y=0.
For 2nd page x=320, y=0.
Thus we can get the current page value by dividing the contentOffset.x by page-width.
Thus, 0/320 = 0, means 1st page. 320/320 = 1, means 2nd page, and so on.
Thus, if we have current page value with us, we can determine in which direction the scroll view is moving, as follows:
-(void) scrollViewDidScroll:(UIScrollView *)scrollView{
int currentPageOffset = currentPage * PAGE_WIDTH;
if (self.pageScrollView.contentOffset.x >= currentPageOffset + PAGE_WIDTH) {
// Scroll in right direction. Reached the next page offset.
// Settings for loading the next page.
currentPage = self.pageScrollView.contentOffset.x/PAGE_WIDTH;
}else if (self.pageScrollView.contentOffset.x <= currentPageOffset - PAGE_WIDTH) {
// Scroll in left direction. Reached the previous page offset.
// Setting for loading the previous page.
currentPage = self.pageScrollView.contentOffset.x/PAGE_WIDTH;
}
}