Strange Wobble effect on iPhone 6 Plus simulator - UIDynamics - ios

Video link here: (low quality) where you can see everything wobbling around. https://drive.google.com/file/d/0B6SrhxQY65faS3pfaGF0bXBiVXFncWU0aFdsVWFpUXEwaXJ3/edit?usp=sharing
Fix mentioned below doesn't work when in native (non scaling mode), so not really fix.
[Update] So setting a one pixel section inset on the collection view (I had zero left and right before), makes the weirdness go away, it's really strange, why does this only happen on the iPhone 6 Plus? I don't like how it looks with the single pixel inset so will leave the question open in case someone knows what might be going on.
[Original Question] I am using UIAttachmentBehaviors in my UICollectionView that provide a stretchy feel when browsing the collection. This works fine everywhere, except in the iPhone 6 Plus simulator (on XCode 6.0.1). On iPhone 6 Plus, the collection items rotate and wobble around (when they should only be moving on the Y axis. They continue to move for about a minute and then very slowly settle down, while the actual animation is only supposed to last for a fraction of a second. They show clear X axis movement. Has anyone else noticed similar weirdness with the iPhone 6 plus? I'm wondering if this is a simulator bug or a real issue but don't have an iPhone 6 Plus to test on. It works fine on the iPhone 6 simulator.
My code looks like this, and I can't see how this could cause X coordinate changes:
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
UIScrollView *scrollView = self.collectionView;
CGFloat delta = newBounds.origin.y - scrollView.bounds.origin.y;
CGPoint touchLocation = [self.collectionView.panGestureRecognizer locationInView:self.collectionView];
[self.dynamicAnimator.behaviors enumerateObjectsUsingBlock:^(UIAttachmentBehavior *springBehaviour, NSUInteger idx, BOOL *stop) {
CGFloat yDistanceFromTouch = fabsf(touchLocation.y - springBehaviour.anchorPoint.y);
CGFloat xDistanceFromTouch = fabsf(touchLocation.x - springBehaviour.anchorPoint.x);
CGFloat scrollResistance = (yDistanceFromTouch + xDistanceFromTouch) / 1500.0f;
UICollectionViewLayoutAttributes *item = springBehaviour.items.firstObject;
CGPoint center = item.center;
if (delta < 0) {
center.y += MAX(delta, delta*scrollResistance);
}
else {
center.y += MIN(delta, delta*scrollResistance);
}
item.center = center;
[self.dynamicAnimator updateItemUsingCurrentState:item];
}];
return NO;
}

I have the same problem when trying to use UICollectionView with UIKit Dynamics.
Problem seems to appears when you compute size of items programmatically to fit width of screen, so in this case items interfere with each other while dynamics applies and cannot get to equilibrium. (so this is like corner case when items actually overlaps)
In my case solution is simple - I just decrease size of items to be around 90% of computed value. It will impose some gap between items, but this is doesn't matter in my case.

Related

iOS11 scroll offset - problems with transitions

I am working on an app where after iOS 11 a custom flip-card transition is not working correctly. If you look at the GIF below (purposely slow animation) you can see that the card is not correctly placed on the return-flip, and afterwards "clicks" into place. With iOS 10 and below this doesn't happen. It returns to its original place without the click.
I have looked into the new contentInsetAdjustmentBehaviour and set this to .never to avoid the space between the cards and status bar on top. This however did not solve the problem.
if (#available(iOS 11.0, *)) {
self.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
self.navigationController.navigationBar.prefersLargeTitles = NO;
self.navigationController.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeNever;
}
Does anyone have any idea of where I should look to fix this problem?
UPDATE HACK
I looked at the transition and where the center of the transition is determined. Here I made a small hack for iOS 11 that moved the center 64 points for iPhone 8+ and below and 88 points for iPhone X. But only when I had not started scrolling yet. If I scrolled the center was moved 44 points on all iPhone models. See temporary solution below:
CGPoint targetCenterInContainer = CGPointMake(CGRectGetMidX(newCardFrame), CGRectGetMidY(newCardFrame));
if (#available(iOS 11.0, *)) {
double offset = newCardFrame.origin.y - originalCardFrame.origin.y;
if (offset == 64) {
targetCenterInContainer.y -= 64; // For iPhone 8 Plus and below, with no scroll offset
} else if (offset == 88) {
targetCenterInContainer.y -= 88; // For iPhone X, with no scroll offset
} else {
targetCenterInContainer.y -= 44; // For when there are scroll offset, all iPhones
}
}
This works okay, but not 100%.
I am facing similar problems with all animations from my app, some components after transitions are misplaced, so my recommendation it's just disable animations for ios 11 version I mean put animated value to false until apple fix all this problems.

Zooming UICollectionView with custom layout, but centering on pinch

I'm working on making a large grid using a UICollectionView, and I want it to be zoomable (the entire UICollectionView, not just a single cell) with the standard pinch-to-zoom gesture. The grid in question has a custom UICollectionViewLayout, because I needed it to scroll both horizontally and vertically.
I got the layout working perfectly with this SO answer, so I had a grid that you could move all around on. The short version is that each row of cells is a section of the view, and all the cells are positioned based on a uniform cellSize of (to start with) 50.
Then I worked out the pinch-to-zoom ability using a modified version of this SO answer, where I basically change the layout's cellSize value when the pinch gesture is received, and then invalidate the layout so it re-draws with the slightly larger or smaller layout. Thus, all the cells get bigger or smaller, and we have zooming.
Here's the code for the pinch gesture method:
-(void)didReceivePinchGesture:(UIPinchGestureRecognizer*)gesture {
double newCellSize = [(IMMapViewLayout *)_mainCollectionView.collectionViewLayout cellSize] * gesture.scale;
newCellSize = MIN(newCellSize, 100);
newCellSize = MAX(newCellSize, 15);
[(IMMapViewLayout *)_mainCollectionView.collectionViewLayout setCellSize:newCellSize];
[_mainCollectionView.collectionViewLayout invalidateLayout];
}
And everything was working (almost) perfectly.
My problem is this: it zooms from the top-left corner, not from where the pinch is located. Makes sense, I suppose, since we're redrawing everything and it's all a little bigger, but it's obviously not the desired effect.
My first thought was simply to detect the cell directly under the pinch and then use scrollToItemAtIndexPath:atScrollPosition:animated: to move back to that cell instantaneously, but it doesn't seem to be working, and the animation gets super-choppy anyway. Also, if you're pinching anywhere other than the center of the screen, it would be hard to move it right back to that exact spot repeatedly during the zoom.
Here's what I've got now:
-(void)didReceivePinchGesture:(UIPinchGestureRecognizer*)gesture {
double newCellSize = [(IMMapViewLayout *)_mainCollectionView.collectionViewLayout cellSize] * gesture.scale;
newCellSize = MIN(newCellSize, 100);
newCellSize = MAX(newCellSize, 15);
[(IMMapViewLayout *)_mainCollectionView.collectionViewLayout setCellSize:newCellSize];
[_mainCollectionView.collectionViewLayout invalidateLayout];
if([gesture numberOfTouches] >= 2) {
CGPoint touch1 = [gesture locationOfTouch:0 inView:_mainCollectionView];
CGPoint touch2 = [gesture locationOfTouch:1 inView:_mainCollectionView];
CGPoint mid;
mid.x = ((touch2.x - touch1.x) / 2) + touch1.x;
mid.y = ((touch2.y - touch1.y) / 2) + touch1.y;
NSIndexPath *currentIndexPath = [_mainCollectionView indexPathForItemAtPoint:mid];
[_mainCollectionView scrollToItemAtIndexPath:currentIndexPath atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:NO];
}
}
Can anyone help me make this UICollectionView zoom, in its entirety, but centered on the position of the pinch?

Why am I unable to detect when my UIView I push off screen using UIKit Dynamics is no longer visible?

I'm using UIKit Dynamics to push a UIView off screen, similar to how Tweetbot performs it in their image overlay.
I use a UIPanGestureRecognizer, and when they end the gesture, if they exceed the velocity threshold it goes offscreen.
[self.animator removeBehavior:self.panAttachmentBehavior];
CGPoint velocity = [panGestureRecognizer velocityInView:self.view];
if (fabs(velocity.y) > 100) {
self.pushBehavior = [[UIPushBehavior alloc] initWithItems:#[self.scrollView] mode:UIPushBehaviorModeInstantaneous];
[self.pushBehavior setTargetOffsetFromCenter:centerOffset forItem:self.scrollView];
self.pushBehavior.active = YES;
self.pushBehavior.action = ^{
CGPoint lowestPoint = CGPointMake(CGRectGetMinX(self.imageView.bounds), CGRectGetMaxY(self.imageView.bounds));
CGPoint convertedPoint = [self.imageView convertPoint:lowestPoint toView:self.view];
if (!CGRectIntersectsRect(self.view.bounds, self.imageView.frame)) {
NSLog(#"outside");
}
};
CGFloat area = CGRectGetWidth(self.scrollView.bounds) * CGRectGetHeight(self.scrollView.bounds);
CGFloat UIKitNewtonScaling = 5000000.0;
CGFloat scaling = area / UIKitNewtonScaling;
CGVector pushDirection = CGVectorMake(velocity.x * scaling, velocity.y * scaling);
self.pushBehavior.pushDirection = pushDirection;
[self.animator addBehavior:self.pushBehavior];
}
I'm having an immense amount of trouble detecting when my view actually completely disappears from the screen.
My view is setup rather simply. It's a UIScrollView with a UIImageView within it. Both are just within a UIViewController. I move the UIScrollView with the pan gesture, but want to detect when the image view is off screen.
In the action block I can monitor the view as it moves, and I've tried two methods:
1. Each time the action block is called, find the lowest point in y for the image view. Convert that to the view controller's reference point, and I was just trying to see when the y value of the converted point was less than 0 (negative) for when I "threw" the view upward. (This means the lowest point in the view has crossed into negative y values for the view controller's reference point, which is above the visible area of the view controller.)
This worked okay, except the x value I gave to lowestPoint really messes everything up. If I choose the minimum X, that is the furthest to the left, it will only tell me when the bottom left corner of the UIView has gone off screen. Often times as the view can be rotating depending on where the user pushes from, the bottom right may go off screen after the left, making it detect it too early. If I choose the middle X, it will only tell me when the middle bottom has gone off, etc. I can't seem to figure out how to tell it "just get me the absolute lowest y value.
2. I tried CGRectIntersectsRect as shown in the code above, and it never says it's outside, even seconds after it went shooting outside of any visible area.
What am I doing wrong? How should I be detecting it no longer being visible?
If you take a look on UIDynamicItem protocol properties, you can see they are center, bounds and transform. So UIDynamicAnimator actually modifies only these three properties. I'm not really sure what happens with the frame during the Dynamics animations, but from my experience I can tell it's value inside the action block is not always reliable. Maybe it's because the frame is actually being calculated by CALayer based on center, transform and bounds, as described in this excellent blog post.
But you for sure can make use of center and bounds in the action block. The following code worked for me in a case similar to yours:
CGPoint parentCenter = CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds));
self.pushBehavior.action = ^{
CGFloat dx = self.imageView.center.x - parentCenter.x;
CGFloat dy = self.imageView.center.y - parentCenter.y;
CGFloat distance = sqrtf(dx * dx + dy * dy);
if(distance > MIN(parentCenter.y + CGRectGetHeight(self.imageView.bounds), parentCenter.x + CGRectGetWidth(self.imageView.bounds))) {
NSLog(#"Off screen!");
}
};

Sticky scrollview subview has incorrect frame after scrolling fast. Compilable example

I have spent the past 12 hours futzing with this and I'm going braindead.
view on bitbucket:
https://bitbucket.org/burneraccount/scrollviewexample
git https: https://bitbucket.org/burneraccount/scrollviewexample.git
git ssh: git#bitbucket.org:burneraccount/scrollviewexample.git
^ That is a condensed, self-contained Xcode project that exemplifies the problem I'm having in my real work.
Summary
I am trying to achieve a static-image effect, where the image (a rock in the example) appears stuck to the screen while the lower content scrolls upwards appearing to go above the scrollview.
There's a screen-sized (UIScrollView*)mainScrollView. This scrollview has a (UIView*)contentView. contentView is ~1200 pts long and has two subviews: (UIScrollView*)imageScroller and (UITextView*)textView.
All works well until you scroll up a little bit, then scroll down really fast. The resulting position after movement stops is incorrect. It somehow doesn't update properly in the scrollviewDidScroll delegate method. The fast downward scroll (only after you've dragged up) behaves somewhat correctly, but still results in a misplaced view:
gently dragging does almost nothing, and the velocity of the scroll is directly related to the incorrect offset.
Here is the offending code:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView == self.mainScrollView)
{
CGFloat mainOffset = self.mainScrollView.contentOffset.y;
if (mainOffset > 0 && mainOffset < self.initialImageScrollerFrame.size.height)
{
CGRect imageFrame = self.imageScroller.frame;
CGFloat offset = mainOffset-self.lastOffset;
self.imageScroller.frame = CGRectMake(imageFrame.origin.x,
imageFrame.origin.y + offset, // scroll up
imageFrame.size.width,
imageFrame.size.height - offset); // hide the bottom of the image
self.lastOffset = mainOffset;
}
}
}
I've restructured this in almost every way I could imagine and it always has similar effects. The worst part is how the very simple and straightforward code fails to do what is seems like it's guaranteed to do.
Also odd is that the zoom-effect I use in my real project works fine using the same mechanism to size the same view element. It runs inside (contentOffset < 0) instead of (contentOffset > 0), so it only zoom in on the imageScroller when you're pulling the view below it's normal offset. That leads me to conspire that some data is lost as it crosses the contentOffset 0, but my conspiracies have been shot down all day.
This is example is a lot less complicated than the real project I'm working on, but it properly reproduces the problem. Please let me know if you can't open my project, or can't connect to the repo.
There is likely a largely obvious fix for this, but I am hours past the point of having any new perspective. I will be thoroughly amazed if I ever get this to work.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView == self.mainScrollView)
{
CGFloat mainOffset = self.mainScrollView.contentOffset.y;
if (mainOffset >= 0 && mainOffset < self.initialImageScrollerFrame.size.height)
{
CGRect imageFrame = self.imageScroller.frame;
CGFloat offset = self.mainScrollView.contentOffset.y-self.lastOffset;
if ( imageFrame.origin.y + offset > 0) { //[2]
self.imageScroller.frame = CGRectMake(imageFrame.origin.x,
imageFrame.origin.y + offset,
imageFrame.size.width,
imageFrame.size.height - offset);
self.lastOffset = mainOffset;
}
}else if (mainOffset < 0) { //[1]
self.imageScroller.frame = self.initialImageScrollerFrame;
}
}
}
[1] - When your mainOffset goes below zero, you need to reset your imageScroller frame. ScrollViewDidScroll does not register with every pixel's-worth of movement, it is only called every so often (try logging it, you will see). So as you scroll faster, it's offsets are further apart. This can result in a positive scroll of say +15 becoming a negative scroll on the next call to scrollViewDiDScroll. So your imageScroller.frame.y may get stuck on that +15 offset even when you expect it to be zero.Thus as soon as you detect a negative value for mainOffset, you need to reset your imageScroller frame.
[2] - similarly here you need to ensure here that your imageScroller.frame.y will always be +ve.
These fixes do the job, but I would still consider them "ugly and not production-worthy". For a cleaner approach you might want to reconsider the interplay between these views, and what you are trying to achieve.
By the way, your 'image.png' is actually a jpg. While these renders fine on the simulator, it will break (with compiler errors) on a device.
I'll post my updates here.
One answerer mentioned - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView and while that doesn't apply here, the similar scrollViewDidEndDecelerating: does. In my struggles yesterday I implemented
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
if (self.mainScrollView.contentOffset.y < 10)
{
[UIView animateWithDuration:0.2 animations:^{
self.imageScroller.frame = self.initialImageScrollerFrame;
}];
}
}
and this is somewhat of a workaround, but it's ugly and definitely not production-worthy. It just animated the frame back to its original position if the contentOffset is close enough to 0. It's a step in the right direction, but I'm afraid it doesn't deserve merit as a solution.
----
Also added
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
withVelocity:(CGPoint)velocity
targetContentOffset:(inout CGPoint *)targetContentOffset
{
CGPoint point = *targetContentOffset;
CGFloat offset = point.y;
if (0 <= offset && offset < 10)
{
[UIView animateWithDuration:0.2 animations:^{
self.imageScroller.frame = self.initialImageScrollerFrame;
}];
}
}
which brings me closer to the intended effect, but still remains unusable for a production application.
The same issue but for add another functionality, button for left/right listing UIScrollView, after fast tapping on it, scroll view is broke my brain ;(
I think better scrolling without animation, it preventing glitches for UIScrollView content. May be it is not answer, but...
Hey mate use the below method that might help you out, its a delegate method of UIScrollView:
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
And within this method you can change the scrollview frame as x and y to 0,0

How to smoothly move a UIView with the users finger on iOS

in my app I have a UIImageView representing a chalk. The user can pick that up and drag it over the screen to draw with it.
I implemented that using touchesBegan, touchesMoved and touchesEnded. In touchesMoved I move the center of my UIImageView to the current touch location and draw a line with core graphics from the last to the current location.
This works well but the image view movement isn't very smooth and it also lags behind the touch. I already tried to use a UIPanGestureRecognizer (didn't recognize the gesture) and a UIScrollView in which I placed the chalk (didn't really figure out how to configure it so that it could be moved far enough in all directions).
Can you give me some hints how to improve the quality of my chalk movement?
Thanks!
The following tapGesture method method works smooth for me:
- (void)handlePan:(UIPanGestureRecognizer*)gesture{
View* view = (View*)gesture.view;
if (gesture.state == UIGestureRecognizerStateEnded){
view.dragStartingPoint = CGPointZero;
return;
}
CGPoint point = [gesture locationInView:gesture.view];
if (gesture.state == UIGestureRecognizerStateBegan){
view.dragStartingPoint = point;
view.dragStartingFrame = view.frame;
return;
}
if (CGPointEqualToPoint(view.dragStartingPoint, CGPointZero))
return;
CGFloat x = view.dragVerticallyOnly? view.frame.origin.x: view.dragStartingFrame.origin.x + point.x - view.dragStartingPoint.x;
CGFloat y = view.dragHorizontallyOnly? view.frame.origin.y: view.dragStartingFrame.origin.y + point.y - view.dragStartingPoint.y;
if (self.dragVerticallyOnly == NO){
if (x < view.dragMinPoint.x){
x = view.dragMinPoint.x;
}
else if (x > self.dragMaxPoint.x){
x = view.dragMaxPoint.x;
}
}
if (self.dragHorizontallyOnly == NO){
if (y < view.dragMinPoint.y){
y = view.dragMinPoint.y;
}
else if (y > self.dragMinPoint.y){
y = view.dragMinPoint.y;
}
}
CGFloat deltaX = x - view.frame.origin.x;
CGFloat deltaY = y - view.frame.origin.y;
if ((fabs(deltaX) <= 1.0 && fabs(deltaY) <= 1.0))
return; // Ignore very small movements
[UIView animateWithDuration:0.1 animations:^{
view.frame = CGRectMake(x, y, view.frame.size.width, view.frame.size.height);
}];
}
The most important points are:
Make the movement with the animated option; (one tenth of a second seems to do the job well done).
Avoid making movements when not necessary. Making some calculations will not make it slower, but making unnecessary movements might. However you cannot avoid to many movements otherwise it will not follow the user pan gesture.
Depending on how you want your view to behave, there can be more optimisations that you may want to make.
Remark: My method also takes into account boundaries and movement directions limits that you may define in case you need it. For instance, in my case I added a speed limit. If the user goes beyond a certain pan speed I just ignore further pan gestures and move the view (animated) where the user was pointing to, and at that speed. It may or not make sense in same cases. In mine, it did!
I hope I have been of some help!
look at this component : spuserresizableview
http://cocoacontrols.com/platforms/ios/controls/spuserresizableview
The source code is very simple and can help you to understand the view handling.
Try reducing the size of the Chalk image u are using....
Edited: By size i meant the image file size.
It helped me when i faced similar issue.
I rewrote how the movement is calculated and it is a little smoother now. Not perfect, but it's enough.

Resources