Why is UIView moved to right on offsetBy - ios

I have a scrollview with an UIImageView on it. When the user scrolls, I would like to keep the UIImageView at its original place. I am using offsetBy for this:
func scrollViewDidScroll(scrollView: UIScrollView) {
let offset = scrollView.contentOffset.y
self.headerView!.currentLoadLabel.frame = currentLoadLabelFrame.offsetBy(dx: 0, dy: offset)
}
The UIImageView stays at his y position but moves to the farmost x position at the right of the screen. Why?

try following code:
self.headerView!.currentLoadLabel.frame = currentLoadLabelFrame.offsetBy(dx: scrollView.contentOffset.x, dy: scrollView.contentOffset.y)

Related

UICollectionView with pagination. Keeping the second cell in center of the screen

I am new to iOS.
I am having my collection view inside tableview cell.
There are 3 cells in collection view cell.
I need to show the second cell of collection view in center of the screen as shown in the image and also want to add pagination into it.
Any help will be appreciated.
Image
Thank You
I had to do a similar collection view a few months ago, This is the code that I use:
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let layout = theNameOfYourCollectionView.collectionViewLayout as! UICollectionViewFlowLayout
let cellWidthIncludingSpacing = layout.itemSize.width + layout.minimumLineSpacing // Calculate cell size
let offset = scrollView.contentOffset.x
let index = (offset + scrollView.contentInset.left) / cellWidthIncludingSpacing // Calculate the cell need to be center
if velocity.x > 0 { // Scroll to -->
targetContentOffset.pointee = CGPoint(x: ceil(index) * cellWidthIncludingSpacing - scrollView.contentInset.right, y: -scrollView.contentInset.top)
} else if velocity.x < 0 { // Scroll to <---
targetContentOffset.pointee = CGPoint(x: floor(index) * cellWidthIncludingSpacing - scrollView.contentInset.left, y: -scrollView.contentInset.top)
} else if velocity.x == 0 { // No dragging
targetContentOffset.pointee = CGPoint(x: round(index) * cellWidthIncludingSpacing - scrollView.contentInset.left, y: -scrollView.contentInset.top)
}
}
This code calculates the size of your cell, how many cells have already been shown and once the scroll is finished, adjust it to leave the cell centered.
Make sure you have the pagingEnabled of your collectionView in false if you want to use this code.
Also, implement UIScrollViewDelegatein your ViewController

Change a label position while scrollview is scrolling

I have a label outside of a scrollview. I want to move the label from left to right of screen and vice versa when scrollview scrolling up and down.I wrote this code and it works when scrollview is scrolling in normal speed and when it scrolls very fast the label x position changes very slowly. How I can do that for all scrollview scrolling speeds?
func scrollViewDidScroll(_ scrollView: UIScrollView!) {
let offset = scrollView.contentOffset.y
let imahey = view.convert(placeImgview.frame, from:scrollview).origin.y + placeImgview.frame.width
print(offset)
let ratio: CGFloat = (-offset*1.0 / placeImgview.frame.height)
topBarView.alpha = -ratio
if placeName.frame.origin.x < -20 {
placeName.center = CGPoint(x: placeName.center.x - 3*ratio, y: placeName.center.y + ratio/1.5)
}
if (self.lastContentOffset > scrollView.contentOffset.y) {
placeName.center = CGPoint(x: placeName.center.x + 6*ratio, y: placeName.center.y - 2*ratio)
if offset == -20 {
placeName.frame.origin.x = -view.frame.width/2
placeName.frame.origin.y = 60
}
}
self.lastContentOffset = scrollView.contentOffset.y
}
So, the problem is that the faster you scroll, the larger the "gap" gets inbetween scrollViewDidScroll events.
You probably should consider to move around your label using UIView.animate... that would create a more consistent experience because the animation always has the same speed.
This way you could apply the animation using CGAffineTransform(translationX: , y: )
depending if your contentOffset.y passes a given threshold, whenever that label should appear or disappear.

scroll and pinch in multiple UiScrollViews at the same time

I have an UICollectionView with four custom cells. The cell's delegate is my main view which contains the collectionView and extends a custom cell delegate. Each cell contains a single UIScrollView which in turn contains and displays an UIImageView. For now, I can pinch and doubleTap to zoom in and out and move the image inside the cell's scrollViews.
I have in my application a "lock views" button that should lock the views together, so that if I pinch (zoom or move) in a cell, that change should be "copied" in the other cells. A 10% zoom increase in a cell should trigger a 10% zoom increase in all the others cells (independently to each cell current zoomScale). Similarly, a 10 unit movement to the left in one cell should move the others cells 10 units to the left.
I'm using scrollViewDidScroll(_ scrollView: UIScrollView) method to detect a change in my scrollViews and call my delegate which in turn have access to my others cells in my main UICollectionView
My first problem was that when I used UIScrollView.zoom(to : rect) or UIScrollView.zoomScale it would trigger again scrollViewDidScroll(_ scrollView: UIScrollView) in the others cells thus creating some king of infinite loop, but I managed to fix that by using :
var isZooming = false
func scrollViewCustomZoom(to rect: CGRect, animated : Bool ) {
isZooming = true
scrollView.zoom(to: rect, animated: animated)
isZooming = false
}
And :
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if isZooming { return }
// do stuff, typically call the delegate
}
But now my problem is : what do I need to calculate in my scrollViewDidScroll method, and what do I need to set in each cell in my delegate in order you have the behavior I'm looking for?
I've already tried to play around scrollView.bounds, scrollView.origin or scrollView.size, but couldn't get the scaling factor to behave properly.
Any help would be most welcome and appreciated.
Found a solution to my own question. This solution does not call scrollViewDidScroll() in the others cells, which is exactly what I wanted.
Here is the trick :
// pinch-pan detection :
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let newOrigin = scrollView.bounds.origin
let dx = newOrigin.x - currentScrollViewOrigin.x
let dy = newOrigin.y - currentScrollViewOrigin.y
let newScale = scrollView.zoomScale
let ds = newScale - currentZoomScale
if let d = delegate{
d.notifyMouvementInCell(self, dx: dx, dy: dy, ds: ds)
}
currentScrollViewOrigin = scrollView.bounds.origin
currentZoomScale = scrollView.zoomScale
}
In my delegate :
func notifyMouvementInCell(_ cell: customCell, dx: CGFloat, dy: CGFloat, ds: CGFloat){
if areViewsLocked{
for visibleCell in collectionView.visibleCells as! [IMECasesMultiDisplayCollectionViewCell]{
if visibleCell == cell { continue }
visibleCell.copyMouvementInForeignCell(horOffset: dx, vertOffset: dy, scaleOffset: ds)
}
}
}
And back in my cells :
func copyMouvementInForeignCell(horOffset dx: CGFloat, vertOffset dy: CGFloat, scaleOffset ds: CGFloat){
var newOrigin = scrollView.bounds.origin
newOrigin.x += dx
newOrigin.y += dy
scrollView.bounds.origin = newOrigin
var newScale = scrollView.zoomScale
newScale += ds
let affineTrans = CGAffineTransform(a: newScale, b: 0, c: 0, d: newScale, tx: 0, ty: 0)
// imageView is the view contained in each of my cell's scrollViews
imageView.layer.setAffineTransform(affineTrans)
currentZoomScale = scrollView.zoomScale
currentScrollViewOrigin = scrollView.bounds.origin
}
Hope this will help somebody someday !

How to prevent scrolling left to right of collection view in horizontal scroll direction?

I made a UICollectionView with a horizontal scroll.
I want to scroll only one direction i.e right to left my cell view size is as full view. once I scrolling cell, it should not scroll left to right.
Please Try this,
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let row = scrollView.contentOffset.x / cellWidth
currentIndexShown = Int(row)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.x < cellWidth * CGFloat(currentIndexShown){
scrollView.contentOffset = CGPoint(x: cellWidth * CGFloat(currentIndexShown), y: -20)
scrollView.bounces = false
} else {
scrollView.bounces = true
}
}

Move UIImageView Independently from its Mask in Swift

I'm trying to mask a UIImageView in such a way that it would allow the user to drag the image around without moving its mask. The effect would be similar to how one can position an image within the Instagram app essentially allowing the user to define the crop region of the image.
Here's an animated gif to demonstrate what I'm after.
Here's how I'm currently masking the image and repositioning it on drag/pan events.
import UIKit
class ViewController: UIViewController {
var dragDelta = CGPoint()
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
attachMask()
// listen for pan/drag events //
let pan = UIPanGestureRecognizer(target:self, action:#selector(onPanGesture))
pan.maximumNumberOfTouches = 1
pan.minimumNumberOfTouches = 1
self.view.addGestureRecognizer(pan)
}
func onPanGesture(gesture:UIPanGestureRecognizer)
{
let point:CGPoint = gesture.locationInView(self.view)
if (gesture.state == .Began){
print("begin", point)
// capture our drag start position
dragDelta = CGPoint(x:point.x-imageView.frame.origin.x, y:point.y-imageView.frame.origin.y)
} else if (gesture.state == .Changed){
// update image position based on how far we've dragged from drag start
imageView.frame.origin.y = point.y - dragDelta.y
} else if (gesture.state == .Ended){
print("ended", point)
}
}
func attachMask()
{
let mask = CAShapeLayer()
mask.path = UIBezierPath(roundedRect: CGRect(x: 0, y: 100, width: imageView.frame.size.width, height: 400), cornerRadius: 5).CGPath
mask.anchorPoint = CGPoint(x: 0, y: 0)
mask.fillColor = UIColor.redColor().CGColor
view.layer.addSublayer(mask)
imageView.layer.mask = mask;
}
}
This results in both the image and mask moving together as you see below.
Any suggestions on how to "lock" the mask so the image can be moved independently underneath it would be very much appreciated.
Moving a mask and frame separately from each other to reach this effect isn't the best way to go about doing this. Most apps that do this sort of effect do the following:
Add a UIScrollView to the root view (with panning/zooming enabled)
Add a UIImageView to the UIScrollView
Size the UIImageView such that it has a 1:1 ratio with the image
Set the contentSize of the UIScrollView to match that of the UIImageView
The user can now pan around and zoom into the UIImageView as needed.
Next, if you're, say, cropping the image:
Get the visible rectangle (taken from Getting the visible rect of an UIScrollView's content)
CGRect visibleRect = [scrollView convertRect:scrollView.bounds toView:zoomedSubview];
Use whatever cropping method you'd like on the UIImage to get the necessary content.
This is the smoothest way to handle this kind of interaction and the code stays pretty simple!
Just figured it out. Setting the CAShapeLayer's position property to the inverse of the UIImageView's position as it's dragged will lock the CAShapeLayer in its original position however CoreAnimation by default will attempt to animate it whenever its position is reassigned.
This can be disabled by wrapping both position settings within a CATransaction as shown below.
func onPanGesture(gesture:UIPanGestureRecognizer)
{
let point:CGPoint = gesture.locationInView(self.view)
if (gesture.state == .Began){
print("begin", point)
// capture our drag start position
dragDelta = CGPoint(x:point.x-imageView.frame.origin.x, y:point.y-imageView.frame.origin.y)
} else if (gesture.state == .Changed){
// update image & mask positions based on the distance dragged
// and wrap both assignments in a CATransaction transaction to disable animations
CATransaction.begin()
CATransaction.setDisableActions(true)
mask.position.y = dragDelta.y - point.y
imageView.frame.origin.y = point.y - dragDelta.y
CATransaction.commit()
} else if (gesture.state == .Ended){
print("ended", point)
}
}
UPDATE
Here's an implementation of what I believe AlexKoren is suggesting. This approach nests a UIImageView within a UIScrollView and uses the UIScrollView to mask the image.
class ViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var scrollView: UIScrollView!
var imageView:UIImageView = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
let image = UIImage(named: "point-bonitas")
imageView.image = image
imageView.frame = CGRectMake(0, 0, image!.size.width, image!.size.height);
scrollView.delegate = self
scrollView.contentMode = UIViewContentMode.Center
scrollView.addSubview(imageView)
scrollView.contentSize = imageView.frame.size
let scale = scrollView.frame.size.width / scrollView.contentSize.width
scrollView.minimumZoomScale = scale
scrollView.maximumZoomScale = scale // set to 1 to allow zoom out to 100% of image size //
scrollView.zoomScale = scale
// center image vertically in scrollview //
let offsetY:CGFloat = (scrollView.contentSize.height - scrollView.frame.size.height) / 2;
scrollView.contentOffset = CGPointMake(0, offsetY);
}
func scrollViewDidZoom(scrollView: UIScrollView) {
print("zoomed")
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return imageView
}
}
The other, perhaps simpler way would be to put the image view in a scroll view and let the scroll view manage it for you. It handles everything.

Resources