Horizontal parallax not working as intended - ios

I'm attempting a parallax effect with a UIScrollView whereby there will be an UIImageView and UILabel per 'page', and as the user scrolls through the pages, the UIImageView will disappear off of the current page at a faster rate than the UILabel, while the next UIImageView and UILabel appear.
My code is working perfectly when on the first page as the amplifier is not taken into account, thus not working on any other page; the UIImageView is not centered horizontally within the page, which it should be.
Please can you tell me where I'm going wrong?
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGPoint offset = scrollView.contentOffset;
NSInteger currentPage = floorf(offset.x / CGRectGetWidth(self.view.frame));
if (currentPage >= 0 && currentPage < CINNumberOfWalkthroughs) {
NSInteger lastPage = self.pageControl.currentPage;
self.pageControl.currentPage = currentPage;
float imageViewAmplifier = offset.x / scrollView.contentSize.width;
UIImageView *imageView = self.imageViews[currentPage];
float w = CGRectGetWidth(self.view.frame);
float a = CGRectGetWidth(imageView.frame);
imageView.center = CGPointMake((currentPage * w) + ((w + a) / 2) + (offset.x * imageViewAmplifier), imageView.center.y);
}
}
I used the following calculation to determine the center position of each of the UIImageViews, and then add a portion of the offset to add the moving effect.

I managed to fix the issue by simplifying things - I have the tendency to overcomplicate everything which made this task a lot more difficult than it needed to be.
Instead of working out the current center of the imageView and performing the translation by transforming the center point, I used a simple CGAffineTransformMakeTranslation() which effectively deals with all of this for me. All I had to do from there was work out the actual offset from the origin of the page, and then fix the amplifier, which was corrected by working out the ration between the actual offset and the content size of the scroll view. My working solution can be found below:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGPoint offset = scrollView.contentOffset;
NSInteger currentPage = floorf(offset.x / CGRectGetWidth(self.view.frame));
if (currentPage >= 0 && currentPage < CINNumberOfWalkthroughs) {
self.pageControl.currentPage = currentPage;
UIImageView *imageView = self.imageViews[currentPage];
float multiplier = -1 * (offset.x / scrollView.contentSize.width);
float w = CGRectGetWidth(self.view.frame);
imageView.transform = CGAffineTransformMakeTranslation((offset.x - currentPage * w) * multiplier, 0);
UILabel *instructionLabel = self.instructionLabels[currentPage];
instructionLabel.transform = CGAffineTransformMakeTranslation((offset.x - currentPage * w) * (multiplier - 0.4), 0);
}
}

Related

IOS Autolayout- Multiple images in one horizontal line of subview programmatically

I created a tableview header and added a subview to it.
I am trying to center multiple avatar images(fixed width and height) in a single line of this ui subview.
Max number of images=max(predefined).
Number of images that I would like to fit is n.
When 5 is max number of image,
n=1:
[......................image 1........................... ]
n=2:
[.................image 1.....image2...............]
n=3
[.........image 1.....image2....image3.........]
n=4
[...image 1....image2....image3......image4...]
n=5
[.imag 1....imag2....imag3....imag4....imag5.]
That is the placement should be centred with equal spacings.
How can I achieve this programatically?
I can do this by story board, by placing max images in a row, keeping equal leading and trailing spaces,
and when n=1, i will hide 1,2,4 and 5
while when n=2, i will hide 1,2,5
If you can target a Deployment Target of iOS 9 and up, use UIStackView. It's designed to handle this scenario.
If you can't do that yet, the following algorithm will work. It doesn't use constraints.
CGFloat tableWidth = ...; // This may change if you allow rotation.
CGFloat spacer = ...; // Amount of space between images goes here.
CGFloat imageWidth = images.count > 0 ? [[images[0] size] width] : 0.0;
CGFloat imageHeight = images.count > 0 ? [[images[0] size] height] : 0.0;
CGFloat totalWidth = images.count * imageWidth + (images.count - 1) * spacer;
CGFloat leftEdge = (tableWidth / 2.0) - (totalWidth / 2.0);
for (int i = 0; i < images.count; i++) {
UIImageView *iv = ...; // Create and configure with image[i].
CGRect frame = CGMakeRect(0, 0, imageWidth, imageHeight);
frame.origin.x = leftEdge + (i * imageWidth + i * spacer);
iv.frame = frame;
}

Avoid displaying half UICollectionViewCell

As shown in the image, my requirement is to avoid displaying half cell while scrolling....if i scroll more than half a height of cell and released, the cell should be completed scrolled to top..it is somewhat similar to setPagingEnabled concept...I want to implement same concept for each row.. Thanks in advance
Override
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
in UICollectionViewlayout. The return value is the offset you want to use instead of the one the user scrolled to. Assuming your row's height is 100 and the current proposedContentOffset is (0,80) then you would want to return (0,100) which would then scroll your collection view 20 more points. At that point, your previous row's cells won't be visible anymore.
(void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
CGFloat proposedOffset = targetContentOffset->y;
CGFloat heightOfPage =65;
CGFloat heightOfSpacing = 5.0f;
CGFloat numOfPage = lround(proposedOffset / (heightOfPage + heightOfSpacing));
CGFloat newY = numOfPage * (heightOfPage + heightOfSpacing);
// if the calculated y is bigger then the maximum possible y we adjust accordingly
CGFloat contentHeight = _categoryCollectionView.contentSize.height;
CGFloat collectionViewHeight = _categoryCollectionView.bounds.size.height;
CGFloat maxY = contentHeight - collectionViewHeight;
if (newY > maxY)
{
newY = maxY;
}
targetContentOffset->y = (newY);
}

UIPanGestureRecognizer - Translations and rotations not happening evenly with negatives vs positives

Towards the top of the file I have this:
#define DEGREES_TO_RADIANS(x) (M_PI * x / 180.0)
In viewdidload I have this:
imageView.layer.anchorPoint = CGPointMake(0.5,0.5);
Then finally in the gesture recognizer I have this:
- (IBAction)handlePan:(UIPanGestureRecognizer *)sender {
CGPoint translation = [sender translationInView:self.view];
xPos += translation.x;
if(xPos > 150) xPos = 150;
if(xPos < -150) xPos = -150;
float rotate = 0;
if(xPos >= 50) {
rotate = (xPos - 50) * 0.025;
} else if(xPos <= -50) {
rotate = (xPos + 50) * 0.025;
}
imageView.transform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(rotate)), (xPos * 0.35), 0);
}
This appears to be applying the exact same effects to negatives (left) and positives (right).
With the UIImageView that is being rotated being placed in the center of the screen horizontally, and its anchor set to the center I would expect the maximum in both directions to be cut off equally by the edge of the screen. Instead the image goes much further to the right.
Left effect (negative):
Right effect (positive):
It looks like autolayout changes the anchor point as the image view is rotated and the frame changes.

Horizontal UICollectionView with UIRefreshControl

I have a custom horizontal collection view that has 1 row and I want to add pull to refresh functionality which, by default, appears above my row of cells. I would like the user to be able to pull the collection view from left to right to activate the UIRefreshControl. Any ideas?
Thanks in advance.
Basically the response above tells you how to do in Objective-C a load more in a UICollectionView. However, I believe the question was how to do pull to refresh horizontally on that component.
I don't think you can add a UIRefreshControl horizontally but taking into consideration the previous code and making a conversion to Swift I came up with the following one
DON'T FORGET TO SET YOUR UICollectionView bounce property TO TRUE
func scrollViewDidScroll(scrollView: UIScrollView) {
let offset = scrollView.contentOffset
let inset = scrollView.contentInset
let y: CGFloat = offset.x - inset.left
let reload_distance: CGFloat = -75
if y < reload_distance{
scrollView.bounces = false
UIView.animateWithDuration(0.3, animations: { () -> Void in
scrollView.setContentOffset(CGPointMake(0, 0), animated: false)
}, completion: { (Bool) -> Void in
scrollView.bounces = true
})
}
}
Also and in order to avoid issues with the continuous pulling I added some code to remove the bouncing temporarily, animate de scroll back to the right and then enabling the bouncing again. That will give you the same effect as the UIRefreshControl.
Finally, if you want to have a loading icon my suggestion is to add it behind the controller so when you pull you can see it behind
Just adding the Obj-C version of Julio Bailon's answer, which works for pulling the collectionView from its Top i.e. Left to Right
CGPoint offset = scrollView.contentOffset;
CGRect bounds = scrollView.bounds;
CGSize size = scrollView.contentSize;
UIEdgeInsets inset = scrollView.contentInset;
float y = offset.x - inset.left;
float h = size.width;
float reload_distance = -75; //distance for which you want to load more
if(y < reload_distance) {
// write your code getting the more data
NSLog(#"load more rows");
}
For this you need to implement the UIScrollViewDelegate method
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGPoint offset = scrollView.contentOffset;
CGRect bounds = scrollView.bounds;
CGSize size = scrollView.contentSize;
UIEdgeInsets inset = scrollView.contentInset;
float y = offset.x + bounds.size.width - inset.right;
float h = size.width;
float reload_distance = 75; //distance for which you want to load more
if(y > h + reload_distance) {
// write your code getting the more data
NSLog(#"load more rows");
}
}

UIScrollView with subView Zooming

I Want to implement a scrollview that has several UIViews inside of it. The left-most item needs to be larger than the rest of the items. So my problem is this. Whenever an item is leaving the screen to the left (its origin.x is less than 15), I need to scale the item down from 470x440 to 235x220 pixels. This is fairly simple to implement. The problem is that the item that is moved to the left of pixel 480 needs to be zoomed in from 235x220 pixels to 470x440 pixels AND it needs to be moved to the left by 235 pixels (so as to not cover the item to its right, but rather move into the space that the leaving element left when it "shrunk".
I have tried a few different approaches to this, but I cannot the the animation to look good, and there is a bunch of glitches here and there.
Does anyone have any idea how I might go about implementing this type of feature ? Note that I do not want to zoom, but I want to resize the elements inside the scroll view in such a way that the left-most element (that is visible on the screen) is double the size of the other elements.
In case anyone else might be interested, I ended up with the following inside scrollViewDidScroll:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
float contentOffset = scrollView.contentOffset.x;
int leavingElementIndex = [_scrollView indexOfElementLeavingScene:scrollView.contentOffset.x];
int entereingElementIndex = leavingElementIndex + 1;
if (leavingElementIndex >= 0 && contentOffset > 0) {
CGRect leavingFrame = [[[scrollView subviews] objectAtIndex:leavingElementIndex] frame];
CGRect enteringFrame = [[[scrollView subviews] objectAtIndex:entereingElementIndex] frame];
float scalePerentage = (contentOffset - (_scrollView.smallBoxWidth * leavingElementIndex))/(_scrollView.smallBoxWidth);
enteringFrame.size.width = _scrollView.smallBoxWidth + (_scrollView.smallBoxWidth * scalePerentage);
enteringFrame.size.height = _scrollView.smallBoxHeight + (_scrollView.smallBoxHeight * scalePerentage);
enteringFrame.origin.x = [_scrollView leftMostPointAt:entereingElementIndex] - (_scrollView.smallBoxWidth * scalePerentage);
[[[scrollView subviews] objectAtIndex:entereingElementIndex] setFrame:enteringFrame];
leavingFrame.size.width = _scrollView.largeBoxWidth - (_scrollView.smallBoxWidth * scalePerentage);
leavingFrame.size.height = _scrollView.largeBoxHeight - (_scrollView.smallBoxHeight * scalePerentage);
[[[scrollView subviews] objectAtIndex:leavingElementIndex] setFrame:leavingFrame];
//Reset the other visible frames sizes
int index = 0;
for (UIView *view in [scrollView subviews]) {
if([view isKindOfClass:[SlidingView class]] && index > entereingElementIndex) {
CGRect frame = view.frame;
frame.size.width = _scrollView.smallBoxWidth;
frame.size.height = _scrollView.smallBoxHeight;
frame.origin.x = [_scrollView leftMostPointAt:index];
[view setFrame:frame];
}
index++;
}
}
}
This is what it looks like in the end:
End Result http://stuff.haagen.name/iOS%20scroll%20resize.gif

Resources