how to allow infinite horizontal scrolling in swift 3 scrollView - ios

Let say I have 3 images in the scroll view. Now I am able to scroll it horizontally in either way. However, when reached the image #3, how to allow it to continuous scroll to image #1.
override func viewDidAppear(_ animated: Bool) {
//allow scrolling even outside the scroll view
view.addGestureRecognizer(scrollView.panGestureRecognizer)
var contentWidth : CGFloat = 0.0
let scrollWidth = scrollView.frame.size.width
// append 3 images
for x in 0...2 {
let image = UIImage(named: "icon\(x).png")
let ImageView = (UIImageView(image:image))
images.append(ImageView)
var newX : CGFloat = 0.0
newX = scrollWidth/2 + scrollWidth * CGFloat(x)
scrollView.addSubview(ImageView)
//set the position of the image to center of the screen
ImageView.frame = CGRect(x: newX - 75, y: (scrollView.frame.size.height/2)-75, width: 150, height: 150)
contentWidth += newX
}
scrollView.clipsToBounds = false
scrollView.contentSize = CGSize(width: contentWidth, height: view.frame.size.height)
}

Related

Swift :- How to add offset to UIImageview inside UIScrollview in Swift?

In my iOS app, I have a scroll view which has the image view to scroll and zoom images.
What I am unable to do is launch the image view with pre-defined offset and zoom value.
Right now, on launch, the original image is shown which I can zoom-in/out and scroll, but I wanted to zoom-in/out with an offset with default values.
On setting the scrollview.zoomScale, I am not able to scroll the image completely.
Code below:-
let scroller = UIScrollView()
scroller.translatesAutoresizingMaskIntoConstraints = false
scroller.minimumZoomScale = 1
scroller.maximumZoomScale = 5
return scroller
}()
let imageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFit
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
The first step:
scrollView = UIScrollView(frame: view.bounds)
view.addSubview(scrollView)
imageView.center = view.center
scrollView.addSubview(imageView)
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 2.0
scrollView.delegate = self
Step 2: implement the UIScrollViewDelegate method
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
Finally:
private func setImagePosition(_ image: UIImage) {
imageView.image = image
let size = imageSize(WithScreen: image)
imageView.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
scrollView?.contentSize = size
if let scrollView = scrollView, size.height < scrollView.bounds.size.height {
let offsetY = (scrollView.bounds.size.height - size.height) * 0.5
scrollView.contentInset = UIEdgeInsets(top: offsetY, left: 0, bottom: offsetY, right: 0)
}
}
private func imageSize(WithScreen image: UIImage) -> CGSize {
var size = UIScreen.main.bounds.size
size.height = image.size.height * size.width / image.size.width
return size
}

How to show image center position inside focus view while scrolling and crop image

I'm trying to zoom image and crop image along the focusView line.
But image's position isn't center while scrolling.
Also cropImage() doesn't crop properly.
I've already tried below code.
setup() image shows center but after scrolling, image position isn't center.
Also cropImage()
class CropImageView: UIView {
#IBOutlet weak var focusView: UIImage! // crop frame
#IBOutlet weak var scrollView: UIScrollView!
var previewImage: UIImage?
var previewImageView: UIImageView!
var zoomScaleView: UIView!
func setup() {
scrollView.delegate = self
zoomScaleView = UIView(frame: .zero)
scrollView.addSubview(zoomScaleView)
previewImageView = UIImageView(frame: .zero)
previewImageView.image = previewImage
zoomScaleView.addSubview(previewImageView)
scrollView.minimumZoomScale = 1
scrollView.maximumZoomScale = 3
previewImageView.frame = CGRect(x: 0, y: 0,width: focusView.frame.width, height: focusView.frame.width)
scrollView.contentSize = CGSize(width: focusView.frame.width, height: focusView.frame.width)
scrollView.contentOffset = CGPoint(x: 0, y: 0)
previewImageView.contentMode = .scaleAspectFit
previewImageView.backgroundColor = .red
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
let scrollZoomScale = scrollView.zoomScale
if scrollZoomScale == scrollView.minimumZoomScale {
scrollView.contentInset = UIEdgeInsets.zero
return
}
if previewImageView.contentClippingRect.width <= previewImageView.contentClippingRect.height {
let difference = (previewImageView.frame.width * scrollZoomScale - previewImageView.contentClippingRect.width * scrollZoomScale)
scrollView.contentSize = CGSize(width: previewImageView.frame.width * scrollZoomScale - difference,
height: previewImageView.frame.height * scrollZoomScale)
scrollView.contentInset = UIEdgeInsets.init(
top: 0,
left: -difference/2,
bottom: (self.frame.height - focusView.frame.maxY),
right: difference/2)
} else {
let difference = (previewImageView.frame.height * scrollZoomScale - previewImageView.contentClippingRect.height * scrollZoomScale)
scrollView.contentSize = CGSize(width: previewImageView.frame.width * scrollZoomScale,
height: previewImageView.frame.height * scrollZoomScale - difference)
scrollView.contentInset = UIEdgeInsets.init(
top: -difference/2,
left: 0 ,
bottom: (self.frame.height - focusView.frame.maxY) + difference/2,
right: 0)
}
}
func cropImage() -> UIImage {
return previewImageView.image?.cgImage?.cropping(to: focusView.frame)
}
}
extension UIImageView {
var contentClippingRect: CGRect {
guard let image = image else { return bounds }
guard contentMode == .scaleAspectFit else { return bounds }
guard image.size.width > 0 && image.size.height > 0 else { return bounds }
let scale: CGFloat
if image.size.width > image.size.height {
scale = bounds.width / image.size.width
} else {
scale = bounds.height / image.size.height
}
let size = CGSize(width: image.size.width * scale, height: image.size.height * scale)
let x = (bounds.width - size.width) / 2.0
let y = (bounds.height - size.height) / 2.0
return CGRect(x: x, y: y, width: size.width, height: size.height)
}
}
[Setup]
[After zooming]
I'm struggling with this problem for long time.
Is there any solution?
Thank you.

How to use UIScrollView to show edge of the next view

I am new in ios development, i want to show edge of next view using scrollview initial i got help from this Link, here is my view hierarchy
1) I added scrollview from story board to view controllers's top view
2) I added a view as container view and collection view programmatically as subviews of scrollview.
I displayed the edge of next view but when i go to next page things are not smoothly, i do not know how to handle this massy thing and also i do not know which approach is best for achieving this particular task. here is my code. I'm really stuck and I really don't know what to do.
func addCollectionViewsInsideScrollView(){
scrollView?.delegate = self;
scrollView?.isPagingEnabled=true
scrollView.indicatorStyle = UIScrollViewIndicatorStyle.white
for i in 0...2 {
if i == 0 {
frame = CGRect(x: scrollWidth * CGFloat (i), y: 0, width: scrollWidth - 45,height: scrollHeight)
subView1 = UIView(frame: frame)
subView1.backgroundColor = .white
scrollView?.backgroundColor = .white
scrollView?.addSubview(subView1)
subView1.addSubview(collectionView0)
collectionView0.frame = CGRect(x: subView1.bounds.origin.x, y: 0, width: subView1.bounds.width,height: subView1.bounds.height)
}
if i == 1 {
frame = CGRect(x: scrollWidth * CGFloat (i) - 20, y: 0, width: scrollWidth - 45,height: scrollHeight)
subView2 = UIView(frame: frame)
scrollView?.addSubview(subView2)
subView2.addSubview(collectionView1)
collectionView1.frame = CGRect(x: subView2.bounds.origin.x, y: 0, width: subView2.bounds.width,height: subView2.bounds.height)
}
if i == 2 {
frame = CGRect(x: scrollWidth * CGFloat (i) - 40, y: 0, width: scrollWidth - 45,height: scrollHeight)
subView3 = UIView(frame: frame)
scrollView?.addSubview(subView3)
subView3.addSubview(collectionView2)
collectionView2.frame = CGRect(x: subView3.bounds.origin.x, y: 0, width: subView3.bounds.width,height: subView3.bounds.height)
}
}
scrollView?.contentSize = CGSize(width: (scrollWidth * 3), height: scrollHeight)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
setIndiactorForCurrentPage()
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if myPageNo == 1 {
frame = CGRect(x: scrollWidth - 20, y: 0, width: scrollWidth - 40,height: scrollHeight)
let subView = UIView(frame: frame)
self.scrollView?.addSubview(subView)
subView.addSubview(collectionView1)
collectionView1.frame = CGRect(x: subView.bounds.origin.x, y: 0, width: subView.bounds.width,height: subView.bounds.height)
myPageNo -= 0
}
if myPageNo == 2{
frame = CGRect(x: scrollWidth * 2 - 20, y: 0, width: scrollWidth,height: scrollHeight)
let subView = UIView(frame: frame)
self.scrollView.addSubview(subView)
subView.addSubview(collectionView2)
collectionView2.frame = CGRect(x: subView.bounds.origin.x, y: 0, width: subView.bounds.width,height: subView.bounds.height)
myPageNo -= 1
}
}
func setIndiactorForCurrentPage() {
let page = (scrollView?.contentOffset.x)!/scrollWidth
print(scrollView?.contentOffset.x ?? 0)
pageControl?.currentPage = Int(page.rounded())
myPageNo = Int(page.rounded())
if myPageNo == 1 {
setFrame(pageNo: 1)
}
if myPageNo == 2{
setFrame(pageNo: 2)
}
}
func setFrame(pageNo: Int){
if(pageNo == 1){
frame = CGRect(x: scrollWidth + 2, y: 0, width: scrollWidth - 40,height: scrollHeight)
let subView = UIView(frame: frame)
scrollView?.addSubview(subView)
subView.addSubview(collectionView1)
collectionView1.frame = CGRect(x: subView.bounds.origin.x, y: 0, width: subView.bounds.width,height: subView.bounds.height)
}
else if(pageNo == 2){
frame = CGRect(x: scrollWidth * 2, y: 0, width: scrollWidth,height: scrollHeight)
let subView = UIView(frame: frame)
scrollView?.addSubview(subView)
subView.addSubview(collectionView2)
collectionView2.frame = CGRect(x: subView.bounds.origin.x, y: 0, width: subView.bounds.width,height: subView.bounds.height)
}
}
when I back from last page to previous one, all is good but when I go first page to next view i am unable to handle showing edge of next view.
So far I achieved this thing but this is not what i want. I want to control uiscroll using tap gesture and alse want to show last page with left align
`this is my code func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
var visibleRect = CGRect()
visibleRect.origin = collectionView.contentOffset
visibleRect.size = collectionView.bounds.size
let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
let visibleIndexPath: IndexPath = collectionView.indexPathForItem(at: visiblePoint)
collectionView.scrollToItem(at: visibleIndexPath, at: .left, animated: true)
indexPathArray.removeAll()
}
`
I have been working on several projects where you would have like a peek at the next item you could view. The best solution is probably a UICollectionView as you would not load every UIViewController into view immediately but only when it's almost up. The cell re-use of UICollectionView takes care of that.
Make sure the cell size (which you can calculate depending on the size of your screen) will be something like width - 40px so you just see the edge of the next cell. It's totally possible to have a UIViewController in every cell, in fact you could even do it via Interface Builder nowadays.
UICollectionView already implements UIScrollView so no need to mess with UIScrollView manually. The only thing you need to do is that at the moment somebody stops scrolling you decide which cell you want to scroll to (the next one or stay on the current one) and scroll to that cell animated. For this you need to add a gesture recognizer:
Intercepting pan gestures over a UIScrollView breaks scrolling
Then scroll to the cell most visible when the user stops scrolling:
https://developer.apple.com/documentation/uikit/uicollectionview/1618046-scrolltoitematindexpath
For this you need to know which cell is most visible at that moment. This calculation can be a bit difficult but the gist is that you need to know which cells are in indexPathsForVisibleItems and then see according to their content- or scrollOffset which one is more into view than the other(s). The indexPath of that one should be the indexPath of the one you want to scroll into view.
This solution scales up to millions of items since you're only loading the cells you actually (are about to) see.
If you have many pages I wouldn't do with scroll view but here is my sample code to show next page in scrollview.
class ExampleViewController: UIViewController {
var scrollView = UIScrollView()
var container1 = UIView()
var container2 = UIView()
var container3 = UIView()
override func viewDidLoad() {
super.viewDidLoad()
scrollView.isPagingEnabled = true
scrollView.clipsToBounds = false
view.addSubview(scrollView)
container1.backgroundColor = .red
container2.backgroundColor = .blue
container3.backgroundColor = .yellow
scrollView.addSubview(container1)
scrollView.addSubview(container2)
scrollView.addSubview(container3)
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
scrollView.frame = CGRect(x: 0, y: 0, width: view.bounds.width - peekAmount, height: view.bounds.height)
scrollView.contentSize = CGSize(width: 3 * scrollView.frame.width, height: scrollView.frame.size.height)
container1.frame.size = containerSize
container2.frame.size = containerSize
container3.frame.size = containerSize
var xPosition: CGFloat = 0
container1.frame.origin = CGPoint(x: xPosition, y: 0)
xPosition = container1.frame.maxX
container2.frame.origin = CGPoint(x: xPosition, y: 0)
xPosition = container2.frame.maxX
container3.frame.origin = CGPoint(x: xPosition, y: 0)
}
var containerSize: CGSize {
return CGSize(width: scrollView.frame.size.width, height: scrollView.frame.size.height)
}
var peekAmount: CGFloat {
return 80
}
}
There are many different ways to achieve your needs but this is simple enough to give you an idea. I didn't add the page control since you already have the logic.
I think, you use UICollectionView to show edge of the next view.

scrollView images are not in the center using midX?

I used two statements in my code when initializing newX variable which I believe they should give the same result but one didn't, which are:
( scrollView.frame.size.width / 2 ) - 75
And
scrollView.frame.midX - 75
here is my code, look for the comment at the middle:
class ViewController: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
var images = [UIImageView]()
var contentWidth: CGFloat = 0.0
override func viewDidAppear(_ animated: Bool) {
for i in 0...2 {
let image = UIImage(named: "icon\(i).png")
let imageView = UIImageView(image: image)
// Here is the problem
let newX = (scrollView.frame.size.width / 2 + scrollView.frame.size.width * CGFloat(i)) - 75
let newY = scrollView.frame.midY - 75
imageView.frame = CGRect(x: newX, y: newY, width: 150, height: 150)
scrollView.addSubview(imageView)
contentWidth += scrollView.frame.size.width
}
scrollView.contentSize = CGSize(width: contentWidth, height: view.frame.size.height)
scrollView.clipsToBounds = false
scrollView.backgroundColor = UIColor.purple
}}
The first one perfectly centred the images, but when I replaced it with the second on it didn't center them as shown in the picture here
Code Representation . why?
The difference is that midX is considering also the position of the scrollview's origin, so midX would be half the width of your scrollView + the X origin of your scrollView. (Similar to how minX is not always 0)
Width, on the other hand, is, well, the width of the scrollView, which is absolute.

How to disable vertical scrolling UIScrollView?

Problem
http://im2.ezgif.com/tmp/ezgif-2269702568.gif
I've set the contentSize to the height that I want. However, my scrollview still has vertical scrolling. In fact there's lots of empty white space, which I'm not sure why.
And I'm not sure if it's because I do: contentOffset = CGPoint(x:0, y: self.frame.minY). But the reason I did that was because when I placed my buttons in the scroll view, I would have to scroll down to see them. contentOffset allows the buttons to be on the scrollview when it is loaded.
If someone could disable the vertical scrolling while still having the buttons show up that would be great!
Code
In my view controller I set up the custom scrollview:
class ScheduleViewController: UIViewController {
private var scheduleBar: ScheduleBar!
private let barHeight: CGFloat = 55
override func viewDidLoad() {
super.viewDidLoad()
scheduleBar = ScheduleBar(frame: CGRect(x: 0, y: (self.navigationController?.navigationBar.frame.maxY)!, width: self.view.bounds.width, height: barHeight))
scheduleBar.setUp(buttonsData: times, selected: 2)
self.view.addSubview(scheduleBar)
}
}
In the custom scrollview I set up my scrollview further:
class ScheduleBar: UIScrollView {
private var buttons: [UIButton]!
private var bar: UIView!
private var buttonWidth: CGFloat!
private var buttonPadding: CGFloat = 20
func setUp(buttonsData: [String], selected: Int){
self.backgroundColor = UIColor.white
buttons = []
for time in buttonsData{
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 50, height: self.bounds.height))
button.setTitle(time, for: UIControlState.normal)
button.setTitleColor(Color.black, for: UIControlState.normal)
button.titleLabel?.font = UIFont(name: "HelveticaNeue-Medium", size: 13.0)
button.sizeToFit()
buttonWidth = button.bounds.width + buttonPadding
buttons.append(button)
}
for i in 0...(buttons.count-1){
if i == 0 {
buttons[i].frame = buttons[i].frame.offsetBy(dx:buttonPadding/2, dy: 0)
}else if i == buttons.count-1{
buttons[i].frame = buttons[i].frame.offsetBy(dx: CGFloat(i) * buttonWidth + buttonPadding/2, dy: 0)
}
else{
buttons[i].frame = buttons[i].frame.offsetBy(dx: CGFloat(i) * buttonWidth + buttonPadding, dy: 0)
}
buttons[i].center.y = (self.bounds.height/2)
addSubview(buttons[i])
}
bar = UIView(frame: CGRect(x: 0, y: self.bounds.height - 3.0, width: buttons[0].bounds.width + buttonPadding, height: 3.0))
bar.backgroundColor = Color.red
select(index: selected, animation: false)
addSubview(bar)
self.contentSize = CGSize(width: CGFloat(buttons.count) * buttonWidth + buttonPadding, height: self.bounds.height)
//reset origin of scrollview
self.contentOffset = CGPoint(x:0, y: self.frame.minY)
}
func select(index: Int, animation: Bool){
func select() -> Void{
if index == 0{
bar.frame.origin.x = CGFloat(0)
}else{
bar.frame.origin.x = buttons[index].frame.minX - buttonPadding/2
}
}
if animation {
UIView.animate(withDuration: 0.7, animations: {select()})
}else{
select()
}
scrollRectToVisible(CGRect(x: buttons[index].frame.minX, y: self.frame.minY, width: buttons[index].bounds.width, height: buttons[index].bounds.height), animated: true)
}
Try setting the contentSize's height to the scrollView's height. Then the vertical scroll should be disabled because there would be nothing to scroll vertically.
scrollView.contentSize = CGSizeMake(scrollView.contentSize.width,scrollView.frame.size.height);
Set scheduleBar.contentSize = (to your desired height so it wont have enough space to scroll up and down)
like:
scheduleBar.frame = CGRectMake(width: 100, height: 200);
scheduleBar.contentSize = CGSizeMake(scheduleBar.contentSize.width,scheduleBar.frame.size.height);
//should disable scroll for both vertical and horizontal

Resources