I have a UIScrollView that when I tilt the iPhone, I want the view to scroll. Please note that I want to scroll two dimensionally, upwards and side to side. I've looked everywhere for answers, Like this. Here's some code:
self.motionManager.startDeviceMotionUpdatesToQueue(NSOperationQueue.mainQueue(), withHandler: {
(motion:CMDeviceMotion!, error:NSError!) in
let xRotationRate = motion.rotationRate.x
let yRotationRate = motion.rotationRate.y
let zRotationRate = motion.rotationRate.z
let xoffset = CGFloat(self.scrollView.contentOffset.x) + CGFloat(xRotationRate)
let contentOffset:CGPoint = CGPointMake(offset, 0)
self.scrollView.setContentOffset(contentOffset, animated:true)
})
Even with this extremely simplified code, I can't get feedback on my phone that is consistent with the rotation of the device. All the views in the scroll view just seem to slowly move to the top of the screen. How would you scroll two dimensionally by tilting the device.
Related
I have UICollectionView which I'm dragging from code (don't ask me why it's very long story:)).
And my code is working pretty well:
func move(prevPoint: CGPoint, curPoint: CGPoint) {
let xDiff = curPoint.x - prevPoint.x
let yDiff = curPoint.y - prevPoint.y
let xSign = xDiff == 0 ? 1 : (xDiff / abs(xDiff))
let ySign = yDiff == 0 ? 1 : (yDiff / abs(yDiff))
let x = max(min(abs(xDiff), maxPickerStep), minPickerStep) * -xSign * xMultiplier
let y = max(min(abs(yDiff), maxPickerStep), minPickerStep) * -ySign
let offset = CGPoint(x: collectionView.contentOffset.x + x, y: collectionView.contentOffset.y)
let cell = (collectionView.visibleCells.first as? ColorsCollectionViewCell)
let innerOffset = cell?.colorCollectionView.contentOffset ?? .zero
let inset = (cell?.colorCollectionView.contentInset.top ?? 0) * 2
let innerYContentOffset = min(max(innerOffset.y + y, -inset), (cell?.colorCollectionView.contentSize.height ?? 0) - inset)
cell?.colorCollectionView.contentOffset = CGPoint(x: innerOffset.x, y: innerYContentOffset)
collectionView.contentOffset = offset
}
But in addition to scrolling, I want to achieve the same effect as in UICollectionView when scrollView moves by inertia after user takes away finger. Thanks.
First thing first, I think that moving the scroll view manually is most certainly a thing I would avoid.
Probably there is something much simpler to fulfill the behavior you need.
So I highly suggest you, and any other reader, to not go further in the reading of this post and, instead, go ahead and try to solve the problem that guided you here in the first place.
You could also ask another question here on Stack Overflow to maybe get help to try to avoid you to manually update the scrollView position.
So if you are still reading, this article is probably the way to go with implementing something that really feels like a UIScrollView. Doing anything else will probably really look and feel awful.
Basically it consists of using UIKit Dynamics to control the inertia.
So you can create an object that conforms to UIDynamicItem (with a non-zero CGRect), and change its center instead of the scrollView contentOffset, than use a UIDynamicAnimator and its UIDynamicBehavior to set up the inertia and to connect the changes during the animation to the corresponding contentOffset in the scrollView using the UIDynamicBehavior's action block.
Assuming that you have an item that is a UIDynamicItem, and an animator that is a UIDynamicAnimator, the handling of the panGesture recognizer would look something like this:
func handlGestureRecognizer(panGesture: UIPanGestureRecognizer) {
switch panGesture.state {
case .began:
self.animator.removeAllBehaviors()
case .changed:
// Update scroll view position
break
case .ended:
var velocity = panGesture.velocity(in: panGesture.view!)
velocity.x = -velocity.x
velocity.y = -velocity.y
// You probably need to check for out of bound velocity too, and also put velocity.x to 0 if the scroll is only scrolling vertically
// This is done to just save the current content offset and then change it alongside the animation from this starting point
item.center = scrollView.contentOffset
let decelerationBehavior = UIDynamicItemBehavior(items: [item])
decelerationBehavior.addLinearVelocity(velocity, for: item)
decelerationBehavior.resistance = 2.0
decelerationBehavior.action = {
// Connect the item center to the scroll contentOffset. Probably something like this:
scrollView.contentOffset = item.center
}
self.animator.addBehavior(decelerationBehavior)
default:
break
}
}
You than just need to play up with the values of the behavior and be careful with the velocity you put into the behavior having extra care in looking at the edge cases (if you scroll over the min/max for example)
PS: After all I've written, I still believe you should strongly consider not doing this and, instead, go with the standard scrollView scrolling, avoiding manual updates.
You can try to play with decelerationRate and see if it satisfies your needs.
collectionView.decelerationRate = UIScrollView.DecelerationRate(rawValue: 1)
In my app as shown in figure I need to rotate the view circular as shown in figures. how can i set that.
green is nbutton and red is nview. my code is :
func handleRotate(recognizer : UIRotationGestureRecognizer)
{
nView.transform = CGAffineTransformRotate(imageViews[i].transform, recognizer.rotation)
nButton.center = CGPointMake((nView.frame.origin.x), (nView.frame.origin.y))
recognizer.rotation = 0
}
Actually I am enable to get the frame of imageView here. Even bounds not working for me..
I am trying to create a parallax effect such as the iPhone homescreen where the background moves as your tilt your phone. I have so far achieved this, but I have one problem still. After I tilt my phone and the background moves, it very slowly moves back into position of being centered.
It's not the constraints. I removed the center x/y constraint and it still slid back slowly as if it was re-calibrating to the new position. The only other constraint is the ratio. So it's not the constraints.
any ideas?
The code is simple:
let leftRightMin = CGFloat(-50.0)
let leftRightMax = CGFloat(50.0)
let upDownMin = CGFloat(-35.0)
let upDownMax = CGFloat(35.0)
let leftRight = UIInterpolatingMotionEffect(keyPath: "center.x", type:
UIInterpolatingMotionEffectType.TiltAlongHorizontalAxis)
leftRight.minimumRelativeValue = leftRightMin
leftRight.maximumRelativeValue = leftRightMax
let upDown = UIInterpolatingMotionEffect(keyPath: "center.y", type:
UIInterpolatingMotionEffectType.TiltAlongVerticalAxis)
upDown.minimumRelativeValue = upDownMin
upDown.maximumRelativeValue = upDownMax
let fxGroup = UIMotionEffectGroup()
fxGroup.motionEffects = [leftRight, upDown]
backgroundImage.addMotionEffect(fxGroup)
Any ideas why it slowly centers the image back after tilting and how to fix it?
This is the behavior of UIInterpolatingMotionEffect. You'll notice it also happens on the home screen, and everywhere else in the system that the effect is used.
It does this because sometimes the user will move their device in such a way that the content interpolates to the maximum position, but does not return to the center or resting position. This means that the position that was once the maximum interpolating position is now the resting center, so the content must move back to its original position in case the device moves again and the effect can continue.
I did not find any mention of this behavior in the UIInterpolatingMotionEffect documentation, but it can be observed everywhere the system uses the effect.
So I am developing an Ipad app that allows the user to solve a jigsaw puzzle. I've worked out getting the panning motion for each piece, but getting them where I want to has not worked properly. I am trying to make a piece snap into it's final destination when it's within a small range, which is followed by a clicking sound.
Here is a bit of code for a single puzzle piece. When my new game button is pressed, an Image View gets set to the corresponding picture, and randomly placed on the canvas.
#IBAction func NewGameTapped(sender: UIButton){
let bounds = UIScreen.mainScreen().bounds
let height = bounds.size.height
let width = bounds.size.width
image1.image = UIImage(named:"puzzleImage1.png")
image1.center.x = CGFloat(100 + arc4random_uniform(UInt32(width)-300))
image1.center.y = CGFloat(100 + arc4random_uniform(UInt32(height)-300))
//Create Panning (Dragging) Gesture Recognizer for Image View 1
let panRecognizer1 = UIPanGestureRecognizer(target: self, action: "handlePanning1:")
// Add Panning (Dragging) Gesture Recognizer to Image View 1
image1.addGestureRecognizer(panRecognizer1)
}
This is where I am having some issues.
func handlePanning1(recognizer: UIPanGestureRecognizer) {
let center = dict1_image_coordinates["puzzleImage1"] as![Int]
let newTranslation: CGPoint = recognizer.translationInView(image1)
recognizer.view?.transform = CGAffineTransformMakeTranslation(lastTranslation1.x + newTranslation.x, lastTranslation1.y + newTranslation.y)
if recognizer.state == UIGestureRecognizerState.Ended {
lastTranslation1.x += newTranslation.x
lastTranslation1.y += newTranslation.y
}
checkPosition(image1, center: center)
}
func checkPosition(image: UIImageView, center: [Int]){
let distance: Double = sqrt(pow((Double(image.center.x) - Double(center[0])),2) + pow((Double(image.center.y) - Double(center[1])),2))
//if the distance is within range, set image to new location.
if distance <= 20{
image.center.x = CGFloat(center[0])
image.center.y = CGFloat(center[1])
AudioServicesPlaySystemSound(clickSoundID)
}
For whatever reason, the puzzle piece only wants to snap to it's spot when the piece begins the game within the acceptable snap distance. I have tried checking for the object position in various different parts of my program, but nothing has worked so far. Any help or other tips are greatly appreciated.
The issue is likely caused by this line
image1.addGestureRecognizer(panRecognizer1)
Usually people add gestureRecognizer on the parentView, or the rootView of the view controller instead of the image1 itself. The benefit is that the parentView never moves, where as the image1 is constantly being transformed, which may or may not affect the recognizer.translationInView(x) method return value.
do this instead:
self.view.addGestureRecognizer(panRecognizer1)
and change to this line in handlePanning1 function:
image1.transform = CGAffineTransformMakeTranslation(lastTranslation1.x + newTranslation.x, lastTranslation1.y + newTranslation.y)
I have an if statement in my app to detect whether or not to move the view up when the keyboard appears. I have a separate one of 3.5/4, 4.7 and 5.5 inch screens.
The ones for the 3.5/4 and 5.5 inch screens work great, however for some reason, the one for the 4.7 inch screen isn't functioning.
This is my code:
if keyboardActive == false && height == 667 && self.entryView.center.y == 333.5 {
If I remove self.entryView.center.y == 333.5 then it works, so that's the problem. I've tried rounding up to 334 and down to 333 but that didn't help.
Does anyone know why the centre y value is not 333.5?
comparing floating point numbers is problematic. See for example: (How should I do floating point comparison?).
Moving your screen based on literals is also problematic. Better would be to get the frame of the keyboard in view local coordinates and resize your view accordingly. Note that if you are using constraints, you will have to adjust the constraints rather than the frame of your view(s).
Here's a handy function I wrote for the purpose:
private func extractKeyboardInfo(userInfo: NSDictionary) -> (keyboardFrame: CGRect, duration: NSTimeInterval, viewAnimationOptions: UIViewAnimationOptions) {
let globalKeyboardFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as NSValue).CGRectValue()
let localKeyboardFrame = self.view.convertRect(globalKeyboardFrame, fromView: nil)
let curve = UInt((userInfo[UIKeyboardAnimationCurveUserInfoKey] as NSNumber).unsignedIntValue << 16)
let viewAnimationOptions = UIViewAnimationOptions.fromRaw(curve)!
let duration = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as NSNumber).doubleValue as NSTimeInterval
return (localKeyboardFrame, duration, viewAnimationOptions)
}