@@ -25,6 +25,7 @@ class ImageViewerController:UIViewController, UIGestureRecognizerDelegate {
2525 return _parent. navBar
2626 }
2727
28+ // MARK: Layout Constraints
2829 private var top : NSLayoutConstraint !
2930 private var leading : NSLayoutConstraint !
3031 private var trailing : NSLayoutConstraint !
@@ -34,7 +35,8 @@ class ImageViewerController:UIViewController, UIGestureRecognizerDelegate {
3435 private var scrollView : UIScrollView !
3536
3637 private var lastLocation : CGPoint = . zero
37-
38+ private var isAnimating : Bool = false
39+
3840 init ( sourceView: UIImageView ? = nil ) {
3941 super. init ( nibName: nil , bundle: nil )
4042 self . sourceView = sourceView
@@ -104,46 +106,20 @@ class ImageViewerController:UIViewController, UIGestureRecognizerDelegate {
104106
105107 override func viewDidAppear( _ animated: Bool ) {
106108 super. viewDidAppear ( animated)
107- guard animateOnDidAppear == true ,
108- let sourceFrame = sourceView? . frameRelativeToWindow ( )
109- else {
110- // No animation
111- return
109+ guard animateOnDidAppear == true else {
110+ // skip animation
111+ return
112112 }
113-
114113 animateOnDidAppear = false
115-
116- let dummyImageView : UIImageView = UIImageView ( frame: sourceFrame)
117- dummyImageView. clipsToBounds = true
118- dummyImageView. contentMode = . scaleAspectFill
119- dummyImageView. alpha = 1.0
120- dummyImageView. image = imageView. image
121- view. addSubview ( dummyImageView)
122- view. sendSubviewToBack ( dummyImageView)
123-
124- sourceView? . alpha = 1.0
125- imageView. alpha = 0.0
126- backgroundView? . alpha = 0.0
127-
128- DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.1 ) { [ weak self] in
129- guard let _self = self else { return }
130- UIView . animate ( withDuration: 0.237 , animations: {
131- dummyImageView. contentMode = . scaleAspectFit
132- dummyImageView. frame = _self. view. frame
133- _self. backgroundView? . alpha = 1.0
134- _self. sourceView? . alpha = 0.0
135- } ) { _ in
136- _self. imageView. alpha = 1.0
137- dummyImageView. removeFromSuperview ( )
138- }
139- }
114+ executeOpeningAnimation ( )
140115 }
141116
142117 override func viewWillLayoutSubviews( ) {
143118 super. viewWillLayoutSubviews ( )
144119 updateMinMaxZoomScaleForSize ( view. bounds. size)
145120 }
146121
122+ // MARK: Add Gesture Recognizers
147123 func addGestureRecognizers( ) {
148124
149125 let panGesture = UIPanGestureRecognizer (
@@ -173,20 +149,13 @@ class ImageViewerController:UIViewController, UIGestureRecognizerDelegate {
173149 singleTapGesture. require ( toFail: doubleTapRecognizer)
174150 }
175151
176- func updateMinMaxZoomScaleForSize( _ size: CGSize ) {
177- let widthScale = size. width / imageView. bounds. width
178- let heightScale = size. height / imageView. bounds. height
179- let minScale = min ( widthScale, heightScale)
180-
181- scrollView. minimumZoomScale = minScale
182- scrollView. zoomScale = minScale
183- scrollView. maximumZoomScale = max ( 1 , minScale) * 2
184- }
185-
186152 @objc
187153 func didPan( _ gestureRecognizer: UIPanGestureRecognizer ) {
154+ guard
155+ isAnimating == false ,
156+ scrollView. zoomScale == scrollView. minimumZoomScale
157+ else { return }
188158
189- if scrollView. minimumZoomScale != scrollView. zoomScale { return }
190159 let container : UIView ! = imageView
191160 if gestureRecognizer. state == . began {
192161 lastLocation = container. center
@@ -204,45 +173,9 @@ class ImageViewerController:UIViewController, UIGestureRecognizerDelegate {
204173 backgroundView? . alpha = 1.0 - abs( diffY/ view. center. y)
205174 if gestureRecognizer. state == . ended {
206175 if abs ( diffY) > 60 {
207- let dummyImageView : UIImageView = UIImageView ( frame: container. frame)
208- dummyImageView. image = imageView. image
209- dummyImageView. clipsToBounds = false
210- dummyImageView. contentMode = . scaleAspectFill
211- view. addSubview ( dummyImageView)
212- imageView. isHidden = true
213-
214- let exitFrame : CGRect = { ( ) -> CGRect in
215- guard let _sourceFrame = self . sourceView? . frameRelativeToWindow ( )
216- else {
217- var imageViewFrame = self . imageView. frame
218- if diffY > 0 {
219- imageViewFrame. origin. y = - imageViewFrame. size. height
220- } else {
221- imageViewFrame. origin. y = view. frame. size. height
222- }
223- return imageViewFrame
224- }
225- return _sourceFrame
226- } ( )
227-
228- UIView . animate ( withDuration: 0.237 , animations: {
229- dummyImageView. frame = exitFrame
230- dummyImageView. clipsToBounds = true
231- self . backgroundView? . alpha = 0.0
232- self . navBar? . alpha = 0.0
233- } ) { _ in
234- self . dismiss ( animated: false ) {
235- dummyImageView. removeFromSuperview ( )
236- self . delegate? . imageViewerDidClose ( self )
237- }
238- }
176+ executeViewDismissalAnimation ( diffY)
239177 } else {
240- UIView . animate (
241- withDuration: 0.237 ,
242- animations: {
243- container. center = self . view. center
244- self . backgroundView? . alpha = 1.0
245- } )
178+ executeCancelAnimation ( )
246179 }
247180 }
248181 }
@@ -268,18 +201,31 @@ class ImageViewerController:UIViewController, UIGestureRecognizerDelegate {
268201 let pointInView = recognizer. location ( in: imageView)
269202 zoomInOrOut ( at: pointInView)
270203 }
204+
205+ func gestureRecognizerShouldBegin(
206+ _ gestureRecognizer: UIGestureRecognizer ) -> Bool {
207+ if let panGesture = gestureRecognizer as? UIPanGestureRecognizer {
208+ let velocity = panGesture. velocity ( in: scrollView)
209+ return abs ( velocity. y) > abs ( velocity. x)
210+ }
211+ return false
212+ }
213+ }
214+
215+ // MARK: Adjusting the dimensions
216+ extension ImageViewerController {
271217
272- func updateConstraintsForSize ( _ size: CGSize ) {
273- let yOffset = max ( 0 , ( size. height - imageView. frame . height ) / 2 )
274- top . constant = yOffset
275- bottom . constant = yOffset
218+ func updateMinMaxZoomScaleForSize ( _ size: CGSize ) {
219+ let widthScale = size. width / imageView. bounds . width
220+ let heightScale = size . height / imageView . bounds . height
221+ let minScale = min ( widthScale , heightScale )
276222
277- let xOffset = max ( 0 , ( size. width - imageView. frame. width) / 2 )
278- leading. constant = xOffset
279- trailing. constant = xOffset
280- view. layoutIfNeeded ( )
223+ scrollView. minimumZoomScale = minScale
224+ scrollView. zoomScale = minScale
225+ scrollView. maximumZoomScale = max ( 1 , minScale) * 2
281226 }
282227
228+
283229 func zoomInOrOut( at point: CGPoint ) {
284230 let newZoomScale = scrollView. zoomScale == scrollView. minimumZoomScale
285231 ? scrollView. maximumZoomScale : scrollView. minimumZoomScale
@@ -291,14 +237,102 @@ class ImageViewerController:UIViewController, UIGestureRecognizerDelegate {
291237 let rect = CGRect ( x: x, y: y, width: w, height: h)
292238 scrollView. zoom ( to: rect, animated: true )
293239 }
240+
241+ func updateConstraintsForSize( _ size: CGSize ) {
242+ let yOffset = max ( 0 , ( size. height - imageView. frame. height) / 2 )
243+ top. constant = yOffset
244+ bottom. constant = yOffset
245+
246+ let xOffset = max ( 0 , ( size. width - imageView. frame. width) / 2 )
247+ leading. constant = xOffset
248+ trailing. constant = xOffset
249+ view. layoutIfNeeded ( )
250+ }
294251
295- func gestureRecognizerShouldBegin(
296- _ gestureRecognizer: UIGestureRecognizer ) -> Bool {
297- if let panGesture = gestureRecognizer as? UIPanGestureRecognizer {
298- let velocity = panGesture. velocity ( in: scrollView)
299- return abs ( velocity. y) > abs ( velocity. x)
252+ }
253+
254+ // MARK: Animation Related stuff
255+ extension ImageViewerController {
256+
257+ private func executeOpeningAnimation( ) {
258+
259+ guard let _sourceView = sourceView else { return }
260+
261+ let dummyImageView : UIImageView = UIImageView (
262+ frame: _sourceView. frameRelativeToWindow ( ) )
263+ dummyImageView. clipsToBounds = true
264+ dummyImageView. contentMode = . scaleAspectFill
265+ dummyImageView. alpha = 1.0
266+ dummyImageView. image = imageView. image
267+ view. addSubview ( dummyImageView)
268+ view. sendSubviewToBack ( dummyImageView)
269+
270+ _sourceView. alpha = 1.0
271+ imageView. alpha = 0.0
272+ backgroundView? . alpha = 0.0
273+ isAnimating = true
274+ DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.1 ) { [ weak self] in
275+ guard let _self = self else { return }
276+ UIView . animate ( withDuration: 0.237 , animations: {
277+ dummyImageView. contentMode = . scaleAspectFit
278+ dummyImageView. frame = _self. view. frame
279+ _self. backgroundView? . alpha = 1.0
280+ _sourceView. alpha = 0.0
281+ } ) { _ in
282+ _self. imageView. alpha = 1.0
283+ dummyImageView. removeFromSuperview ( )
284+ _self. isAnimating = false
285+ }
286+ }
287+ }
288+
289+ private func executeCancelAnimation( ) {
290+ self . isAnimating = true
291+ UIView . animate (
292+ withDuration: 0.237 ,
293+ animations: {
294+ self . imageView. center = self . view. center
295+ self . backgroundView? . alpha = 1.0
296+ } ) { [ weak self] _ in
297+ self ? . isAnimating = false
298+ }
299+ }
300+
301+ private func executeViewDismissalAnimation( _ diffY: CGFloat ) {
302+ isAnimating = true
303+
304+ let dummyImageView : UIImageView = UIImageView ( frame: imageView. frame)
305+ dummyImageView. image = imageView. image
306+ dummyImageView. clipsToBounds = false
307+ dummyImageView. contentMode = . scaleAspectFill
308+ view. addSubview ( dummyImageView)
309+ imageView. isHidden = true
310+
311+ let exitFrame : CGRect = { ( ) -> CGRect in
312+ guard let _sourceFrame = self . sourceView? . frameRelativeToWindow ( )
313+ else {
314+ var imageViewFrame = self . imageView. frame
315+ if diffY > 0 {
316+ imageViewFrame. origin. y = - imageViewFrame. size. height
317+ } else {
318+ imageViewFrame. origin. y = view. frame. size. height
319+ }
320+ return imageViewFrame
321+ }
322+ return _sourceFrame
323+ } ( )
324+
325+ UIView . animate ( withDuration: 0.237 , animations: {
326+ dummyImageView. frame = exitFrame
327+ dummyImageView. clipsToBounds = true
328+ self . backgroundView? . alpha = 0.0
329+ self . navBar? . alpha = 0.0
330+ } ) { _ in
331+ self . dismiss ( animated: false ) {
332+ dummyImageView. removeFromSuperview ( )
333+ self . delegate? . imageViewerDidClose ( self )
334+ }
300335 }
301- return false
302336 }
303337}
304338
@@ -313,6 +347,8 @@ extension ImageViewerController:UIScrollViewDelegate {
313347 }
314348}
315349
350+
351+ // MARK: Shortcuts
316352extension ImageViewerController {
317353
318354 static func create(
0 commit comments