Zoom on UIView contained in UIScrollView - ios

I have some trouble handling zoom on UIScrollView that contains many subviews. I already saw many answers on SO, but none of them helped me.
So, basically what I'm doing is simple : I have a class called UIFreeView :
import UIKit
class UIFreeView: UIView, UIScrollViewDelegate {
var mainUIView: UIView!
var scrollView: UIScrollView!
var arrayOfView: [UIView]?
override init(frame: CGRect) {
super.init(frame: frame)
self.setupView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
fileprivate func setupView() {
// MainUiView
self.mainUIView = UIView(frame: self.frame)
self.addSubview(mainUIView)
// ScrollView
self.scrollView = UIScrollView(frame: self.frame)
self.scrollView.delegate = self
self.addSubview(self.scrollView)
}
func reloadViews(postArray:[Post]?) {
if let postArray = postArray {
print("UIFreeView::reloadVIew.postArraySize = \(postArray.count)")
let size: CGFloat = 80.0
let margin: CGFloat = 20.0
scrollView.contentSize.width = (size * CGFloat(postArray.count))+(margin*CGFloat(postArray.count))
scrollView.contentSize.height = (size * CGFloat(postArray.count))+(margin*CGFloat(postArray.count))
for item in postArray {
let view = buildPostView(item)
self.scrollView.addSubview(view)
}
}
}
fileprivate func buildPostView(_ item:Post) -> UIView {
// Const
let size: CGFloat = 80.0
let margin: CGFloat = 5.0
// Var
let view = UIView()
let textView = UITextView()
let backgroundImageView = UIImageView()
// Setup view
let x = CGFloat(UInt64.random(lower: UInt64(0), upper: UInt64(self.scrollView.contentSize.width)))
let y = CGFloat(UInt64.random(lower: UInt64(0), upper: UInt64(self.scrollView.contentSize.height)))
view.frame = CGRect(x: x,
y: y,
width: size,
height: size)
// Setup background view
backgroundImageView.frame = CGRect(x: 0,
y: 0,
width: view.frame.size.width,
height: view.frame.size.height)
var bgName = ""
if (item.isFromCurrentUser) {
bgName = "post-it-orange"
} else {
bgName = "post-it-white"
}
backgroundImageView.contentMode = .scaleAspectFit
backgroundImageView.image = UIImage(named: bgName)
view.addSubview(backgroundImageView)
// Setup text view
textView.frame = CGRect(x: margin,
y: margin,
width: view.frame.size.width - margin*2,
height: view.frame.size.height - margin*2)
textView.backgroundColor = UIColor.clear
textView.text = item.content
textView.isEditable = false
textView.isSelectable = false
textView.isUserInteractionEnabled = false
view.addSubview(textView)
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
view.addGestureRecognizer(gestureRecognizer)
return view
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return self.scrollView
}
func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {
let translation = gestureRecognizer.translation(in: self.scrollView)
// note: 'view' is optional and need to be unwrapped
gestureRecognizer.view!.center = CGPoint(x: gestureRecognizer.view!.center.x + translation.x, y: gestureRecognizer.view!.center.y + translation.y)
gestureRecognizer.setTranslation(CGPoint.zero, in: self.scrollView)
}
}
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
}
This class is working perfectly, I can scroll through all my views, but I can't zoom-in and zoom-out in order to see less/more views on my screen.
I think the solution is simple, but I can't seem to find a real solution for my problem ..
Thanks !

Related

Imitate SVProgressHUD behavior

I`ve implemented a UIView that display a CustomLottie in the center of the screen, it has show() and hide() methods.
How can I give it the ability of hide() it from another place than where show() was called??
This is the code:
class LottieProgressHUD: UIView {
static let shared = LottieProgressHUD()
let hudView: UIView
var animationView: AnimationView
//options
var hudWidth:CGFloat = 200
var hudHeight:CGFloat = 200
var animationFileName = "coinLoading"
override init(frame: CGRect) {
self.hudView = UIView()
self.animationView = AnimationView()
super.init(frame: frame)
self.setup()
}
required init?(coder aDecoder: NSCoder) {
self.hudView = UIView()
self.animationView = AnimationView()
super.init(coder: aDecoder)
self.setup()
}
func setup() {
self.addSubview(hudView)
show()
}
override func didMoveToSuperview() {
super.didMoveToSuperview()
if let superview = self.superview {
self.animationView.removeFromSuperview()
let screenRect = UIScreen.main.bounds
let screenWidth = screenRect.size.width
let screenHeight = screenRect.size.height
let width: CGFloat = self.hudWidth
let height: CGFloat = self.hudHeight
self.frame = CGRect(x: (screenWidth / 2) - (width / 2) ,y: (screenHeight / 2) - (height / 2), width: width, height: height)
hudView.frame = self.bounds
layer.masksToBounds = true
self.animationView = AnimationView(name: self.animationFileName)
self.animationView.frame = CGRect(x: 0, y: 0, width: self.hudView.frame.width, height: self.hudView.frame.size.height)
self.animationView.contentMode = .scaleAspectFill
self.hudView.addSubview(animationView)
self.animationView.loopMode = .loop
self.animationView.play()
self.hide()
}
}
func show() {
self.isHidden = false
}
func hide() {
self.isHidden = true
}
}
This is how I call it inside the VC:
let progressHUD = LottieProgressHUD.shared
self.view.addSubview(progressHUD)
progressHUD.show()
I would like to call progressHUD.hide() inside the VM
You already have a static property that you can reference the same instance from anywhere. Should be able to call your show and hide methods from any class.
LottieProgressHUD.shared.show()
LottieProgressHUD.shared.hide()

Why UIScrollView's content is not centered?

I am embedding a UIImageView inside UIContentView to make it zoomable and panable (reference. The problem is that the image is not centered correctly. It looks like the entire image content is shifted out of top left corner:
While the printed content Offset is near zero: content offset is: (0.0, -20.0)
Here is my implementation:
import UIKit
class ZoomableImageView: UIScrollView {
private struct Constants {
static let minimumZoomScale: CGFloat = 0.5;
static let maximumZoomScale: CGFloat = 6.0;
}
// public so that delegate can access
public let imageView = UIImageView()
// gw: must be called to complete a setting
public func setImage(image: UIImage) {
imageView.image = image
imageView.bounds = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
self.contentSize = image.size
let scaleFitZoomScale: CGFloat = min(
self.frame.width / image.size.width ,
self.frame.height / image.size.height
)
// reset scale and offset on each resetting of image
self.zoomScale = scaleFitZoomScale
}
// MARK - constructor
init() {
// gw: there is no super.init(), you have to use this constructor as hack
super.init(frame: CGRect(x: 0, y: 0, width: 0, height: 0)) // gw: relies on autolayout constraint later
minimumZoomScale = Constants.minimumZoomScale
maximumZoomScale = Constants.maximumZoomScale
addSubview(imageView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
ViewController:
class ViewController: UIViewController, UIScrollViewDelegate {
let zoomableImageView = ZoomableImageView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(zoomableImageView)
zoomableImageView.delegate = self
}
override func viewDidLayoutSubviews() {
zoomableImageView.frame = view.frame
let image = UIImage(imageLiteralResourceName: "kelly")
zoomableImageView.setImage(image: image)
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
// print("scale factor is: \(scrollView.zoomScale)")
return zoomableImageView.imageView
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
print("scale factor is: \(scrollView.zoomScale)")
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("content offset is: \(scrollView.contentOffset)")
}
}
However, the centering of image works perfectly fine if I don't wrap the UIImageView and UIScrollView under the custom class:
class ViewController: UIViewController, UIScrollViewDelegate {
let imageView: UIImageView = {
let image = UIImage(imageLiteralResourceName: "kelly")
let imageView = UIImageView(image: image)
imageView.bounds = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
return imageView
} ()
let scrollView: UIScrollView = {
let scrollView = UIScrollView()
return scrollView
} ()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
view.addSubview(scrollView)
scrollView.addSubview(imageView)
scrollView.delegate = self
self.scrollView.minimumZoomScale = 0.5;
self.scrollView.maximumZoomScale = 6.0;
}
override func viewDidLayoutSubviews() {
scrollView.frame = view.frame
if let size = imageView.image?.size {
scrollView.contentSize = size
}
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
}
Did I missed something in the first implementation?
In your ZoomableImageView class, you are setting bounds when it should be frame
So change:
imageView.bounds = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
to
imageView.frame = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)

UIScrollView how to restore to original size after UIImageView pinch zoom

I have a collection view containing several images. I can zoom an image in the cell but I want it and the scroll view to return to its original size on a tap gesture. My tap gesture restores the image to it's original size but it's off the screen and the scroll view remains it's zoomed size. Here is my code:
import UIKit
class ZoomingCollectionViewCell: UICollectionViewCell, UIScrollViewDelegate {
let zoomScrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.backgroundColor = .white
scrollView.isUserInteractionEnabled = true
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 6.0
scrollView.clipsToBounds = true
return scrollView
}()
var itemImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.backgroundColor = .white
imageView.isUserInteractionEnabled = true
return imageView
}()
var originalSize: CGRect!
override init(frame: CGRect) {
super.init(frame: frame)
contentView.backgroundColor = .white
zoomScrollView.delegate = self
itemImageView.frame = CGRect(x: 0, y: 0, width: contentView.frame.size.width, height: contentView.frame.size.height)
zoomScrollView.frame = itemImageView.frame
originalSize = itemImageView.frame
let tap = UITapGestureRecognizer(target: self, action: #selector(tapZoomingGesture(_:)))
tap.numberOfTapsRequired = 2
zoomScrollView.addGestureRecognizer(tap)
zoomScrollView.addSubview(itemImageView)
contentView.addSubview(zoomScrollView)
}
func viewForZooming(in zoomScrollView: UIScrollView) -> UIView? {
return itemImageView
}
func tapZoomingGesture(_ sender: UIPinchGestureRecognizer) {
itemImageView.center = itemImageView.superview!.center
itemImageView.transform = .identity
zoomScrollView.frame = originalSize
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This works for me. I just noticed that I used UIPinchGestureRecognizer as the sender but for some reason it works anyway.
func tapZoomingGesture(_ sender: UIPinchGestureRecognizer) {
let newZoomScale: CGFloat = 1.0000001
let scrollViewSize = zoomScrollView.bounds.size
let w = (scrollViewSize.width / newZoomScale)
let h = (scrollViewSize.height / newZoomScale)
let x = w / 6.0
let y = h / 6.0
let rectToZoomTo = CGRect(x, y, w, h)
zoomScrollView.zoom(to: rectToZoomTo, animated: true)
zoomScrollView.frame = itemImageView.frame
itemImageView.center = zoomScrollView.center
}

UIscrollView Paging and Zooming images in swift

Having a image array(Download from server) which i want to show in scrollview with paging and zooming enable but i didn't find any solution. I am searching a lot but i didn't get any solution.
Any help is really appreciating.
Thanks!
In MainController
var mainScrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
var x:CGFloat = 0
mainScrollView = UIScrollView(frame: view.bounds)
mainScrollView.pagingEnabled = true
let images=["image.png","photo.jpg","3.png"]
for var i = 0; i < images.count; i++
{
let imageView = UIImageView(image: UIImage(named: images[i]))
let scrollView = SlideShow(image: imageView, scrollFrame: CGRectMake(x, view.bounds.origin.y, view.bounds.width, view.bounds.height))
x = x + UIScreen.mainScreen().bounds.size.width
mainScrollView.addSubview(scrollView)
}
mainScrollView.contentSize=CGSizeMake(x, self.mainScrollView.frame.size.height);
mainScrollView.contentOffset=CGPointMake(0, 0);
view.addSubview(mainScrollView)
// Do any additional setup after loading the view.
}
In SubClass
public class SlideShow: UIScrollView, UIScrollViewDelegate {
public var imageView = UIImageView()
// MARK: - Life cycle
init(image: UIImageView, scrollFrame: CGRect) {
super.init(frame: scrollFrame)
//image.setToImageView(imageView)
imageView = image
imageView.clipsToBounds = true
imageView.userInteractionEnabled = true
// UIScrollView(frame: bounds)
delegate = self
showsVerticalScrollIndicator = false
showsHorizontalScrollIndicator = false
backgroundColor = UIColor.blackColor()
// contentSize = imageView.bounds.size
// autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
contentOffset = CGPoint(x: 1000, y: 450)
addSubview(imageView)
// setZoomScale()
setupGestureRecognizer()
}
func setupGestureRecognizer() {
let doubleTap = UITapGestureRecognizer(target: self, action: "handleDoubleTap:")
doubleTap.numberOfTapsRequired = 2
addGestureRecognizer(doubleTap)
}
func handleDoubleTap(recognizer: UITapGestureRecognizer) {
if (zoomScale > minimumZoomScale) {
setZoomScale(minimumZoomScale, animated: true)
} else {
setZoomScale(maximumZoomScale, animated: true)
}
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func layoutSubviews() {
setZoomScale()
//setZoomScale(minimumZoomScale, animated: false)
}
public func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return imageView
}
func setZoomScale() {
let imageViewSize = imageView.bounds.size
let scrollViewSize = bounds.size
let widthScale = scrollViewSize.width / imageViewSize.width
let heightScale = scrollViewSize.height / imageViewSize.height
minimumZoomScale = min(widthScale, heightScale)
zoomScale = 1.0
}
public func scrollViewDidZoom(scrollView: UIScrollView) {
let imageViewSize = imageView.frame.size
let scrollViewSize = bounds.size
let verticalPadding = imageViewSize.height < scrollViewSize.height ? (scrollViewSize.height - imageViewSize.height) / 2 : 0
let horizontalPadding = imageViewSize.width < scrollViewSize.width ? (scrollViewSize.width - imageViewSize.width) / 2 : 0
scrollView.contentInset = UIEdgeInsets(top: verticalPadding, left: horizontalPadding, bottom: verticalPadding, right: horizontalPadding)
}
}
Solution 1 :- Agrume Library with load photo url.
There are multiple ways you can use the image viewer (and the included Example project shows them all).
This one is best library for that.
Solution 2 :-
This library provide multiple images and zoom also.

UIView changing its position in swift

How do I make a UIView slide up with a touch of a button from its original position and them bring it back down with a touch of a button? Using Swift and Xcode 6.
I have currently tried this:
#IBOutlet weak var DynView: UIView!
#IBAction func btnUp(sender: AnyObject) {
}
You have to implement an animation changing the DynView position on click. Here's an example:
#IBAction func btnUp(sender: AnyObject) {
let xPosition = DynView.frame.origin.x
let yPosition = DynView.frame.origin.y - 20 // Slide Up - 20px
let width = DynView.frame.size.width
let height = DynView.frame.size.height
UIView.animateWithDuration(1.0, animations: {
dynView.frame = CGRect(x: xPosition, y: yPosition, width: width, height: height)
})
}
Hi create this extends if you want. For Swift
Create File Extends.Swift and add this code
/**
Extension UIView
by DaRk-_-D0G
*/
extension UIView {
/**
Set x Position
:param: x CGFloat
by DaRk-_-D0G
*/
func setX(#x:CGFloat) {
var frame:CGRect = self.frame
frame.origin.x = x
self.frame = frame
}
/**
Set y Position
:param: y CGFloat
by DaRk-_-D0G
*/
func setY(#y:CGFloat) {
var frame:CGRect = self.frame
frame.origin.y = y
self.frame = frame
}
/**
Set Width
:param: width CGFloat
by DaRk-_-D0G
*/
func setWidth(#width:CGFloat) {
var frame:CGRect = self.frame
frame.size.width = width
self.frame = frame
}
/**
Set Height
:param: height CGFloat
by DaRk-_-D0G
*/
func setHeight(#height:CGFloat) {
var frame:CGRect = self.frame
frame.size.height = height
self.frame = frame
}
}
For Use (inherits Of UIView)
inheritsOfUIView.setX(x: 100)
button.setX(x: 100)
view.setY(y: 100)
I kinda combined the two most voted answers into one and updated to Swift 3. So basically created an extension that animates a view moving to a different position:
extension UIView {
func slideX(x:CGFloat) {
let yPosition = self.frame.origin.y
let height = self.frame.height
let width = self.frame.width
UIView.animate(withDuration: 1.0, animations: {
self.frame = CGRect(x: x, y: yPosition, width: width, height: height)
})
}
}
// MARK: - Properties
var bottomViewHeight: CGFloat = 200
var isViewHide = false
private let bottomView: UIView = {
let view = UIView()
view.backgroundColor = .red
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let showHideButton: UIButton = {
let button = UIButton()
button.setTitle("Show / Hide", for: .normal)
button.setTitleColor(.black, for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(showHideButtonTapped(_:)), for: .touchUpInside)
return button
}()
// MARK: - Lifecycle
override func loadView() {
super.loadView()
view.addSubview(bottomView)
NSLayoutConstraint.activate([
bottomView.heightAnchor.constraint(equalToConstant: bottomViewHeight),
bottomView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
bottomView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
bottomView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
])
view.addSubview(showHideButton)
NSLayoutConstraint.activate([
showHideButton.widthAnchor.constraint(equalToConstant: 200),
showHideButton.heightAnchor.constraint(equalToConstant: 50),
showHideButton.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
showHideButton.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor)
])
}
override func viewDidLoad() {
super.viewDidLoad()
showHideView(isShow: isViewHide)
}
// MARK: - Selectors
#objc func showHideButtonTapped(_ sender: UIButton) {
print("👆 HIDE / SHOW BUTTON")
showHideView(isShow: isViewHide)
}
// MARK: - Functions
private func showHideView(isShow: Bool) {
if isShow {
UIView.animate(withDuration: 0.4) {
self.bottomView.transform = CGAffineTransform(translationX: 0, y: self.bottomViewHeight)
}
} else {
UIView.animate(withDuration: 0.4) {
self.bottomView.transform = CGAffineTransform(translationX: 0, y: 0)
}
}
isViewHide = !isViewHide
}

Resources