UIScrollView+SwCapture.swift 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. //
  2. // UIScrollView+SwCapture.swift
  3. // SwViewCapture
  4. //
  5. // Created by chenxing.cx on 16/2/26.
  6. // Copyright © 2016年 Startry. All rights reserved.
  7. //
  8. import Foundation
  9. import WebKit
  10. public extension UIScrollView {
  11. func swContentCapture (_ completionHandler: @escaping (_ capturedImage: UIImage?) -> Void) {
  12. self.isCapturing = true
  13. // Put a fake Cover of View
  14. let snapShotView = self.snapshotView(afterScreenUpdates: false)
  15. snapShotView?.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: (snapShotView?.frame.size.width)!, height: (snapShotView?.frame.size.height)!)
  16. self.superview?.addSubview(snapShotView ?? UIView())
  17. // Backup all properties of scrollview if needed
  18. let bakFrame = self.frame
  19. let bakOffset = self.contentOffset
  20. let bakSuperView = self.superview
  21. let bakIndex = self.superview?.subviews.firstIndex(of: self)
  22. // Scroll To Bottom show all cached view
  23. if self.frame.size.height < self.contentSize.height {
  24. self.contentOffset = CGPoint(x: 0, y: self.contentSize.height - self.frame.size.height)
  25. }
  26. self.swRenderImageView({ [weak self] (capturedImage) -> Void in
  27. // Recover View
  28. let strongSelf = self!
  29. strongSelf.removeFromSuperview()
  30. strongSelf.frame = bakFrame
  31. strongSelf.contentOffset = bakOffset
  32. bakSuperView?.insertSubview(strongSelf, at: bakIndex!)
  33. snapShotView?.removeFromSuperview()
  34. strongSelf.isCapturing = false
  35. completionHandler(capturedImage)
  36. })
  37. }
  38. fileprivate func swRenderImageView(_ completionHandler: @escaping (_ capturedImage: UIImage?) -> Void) {
  39. // Rebuild scrollView superView and their hold relationship
  40. let swTempRenderView = UIView(frame: CGRect(x: 0, y: 0, width: self.contentSize.width, height: self.contentSize.height))
  41. self.removeFromSuperview()
  42. swTempRenderView.addSubview(self)
  43. self.contentOffset = CGPoint.zero
  44. self.frame = swTempRenderView.bounds
  45. // Swizzling setFrame
  46. let method: Method = class_getInstanceMethod(object_getClass(self), #selector(setter: UIView.frame))!
  47. let swizzledMethod: Method = class_getInstanceMethod(object_getClass(self), #selector(UIView.swSetFrame(_:)))!
  48. method_exchangeImplementations(method, swizzledMethod)
  49. // Sometimes ScrollView will Capture nothing without defer;
  50. DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(0.3 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)) { () -> Void in
  51. let bounds = self.bounds
  52. UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.main.scale)
  53. if (self.swContainsWKWebView()) {
  54. self.drawHierarchy(in: bounds, afterScreenUpdates: true)
  55. }else{
  56. self.layer.render(in: UIGraphicsGetCurrentContext()!)
  57. }
  58. let capturedImage = UIGraphicsGetImageFromCurrentImageContext()
  59. UIGraphicsEndImageContext()
  60. method_exchangeImplementations(swizzledMethod, method)
  61. completionHandler(capturedImage)
  62. }
  63. }
  64. // Simulate People Action, all the `fixed` element will be repeate
  65. // SwContentCapture will capture all content without simulate people action, more perfect.
  66. func swContentScrollCapture (_ completionHandler: @escaping (_ capturedImage: UIImage?) -> Void) {
  67. self.isCapturing = true
  68. // Put a fake Cover of View
  69. let snapShotView = self.snapshotView(afterScreenUpdates: true)
  70. snapShotView?.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: (snapShotView?.frame.size.width)!, height: (snapShotView?.frame.size.height)!)
  71. self.superview?.addSubview(snapShotView!)
  72. // Backup
  73. let bakOffset = self.contentOffset
  74. // Divide
  75. let page = floorf(Float(self.contentSize.height / self.bounds.height))
  76. UIGraphicsBeginImageContextWithOptions(self.contentSize, false, UIScreen.main.scale)
  77. self.swContentScrollPageDraw(0, maxIndex: Int(page), drawCallback: { [weak self] () -> Void in
  78. let strongSelf = self
  79. let capturedImage = UIGraphicsGetImageFromCurrentImageContext()
  80. UIGraphicsEndImageContext()
  81. // Recover
  82. strongSelf?.setContentOffset(bakOffset, animated: false)
  83. snapShotView?.removeFromSuperview()
  84. strongSelf?.isCapturing = false
  85. completionHandler(capturedImage)
  86. })
  87. }
  88. fileprivate func swContentScrollPageDraw (_ index: Int, maxIndex: Int, drawCallback: @escaping () -> Void) {
  89. self.setContentOffset(CGPoint(x: 0, y: CGFloat(index) * self.frame.size.height), animated: false)
  90. let splitFrame = CGRect(x: 0, y: CGFloat(index) * self.frame.size.height, width: bounds.size.width, height: bounds.size.height)
  91. DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(0.3 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)) { () -> Void in
  92. self.drawHierarchy(in: splitFrame, afterScreenUpdates: true)
  93. if index < maxIndex {
  94. self.swContentScrollPageDraw(index + 1, maxIndex: maxIndex, drawCallback: drawCallback)
  95. }else{
  96. drawCallback()
  97. }
  98. }
  99. }
  100. }
  101. public extension UIWebView {
  102. func swContentCapture (_ completionHandler: @escaping (_ capturedImage: UIImage?) -> Void) {
  103. self.scrollView.swContentCapture(completionHandler)
  104. }
  105. func swContentScrollCapture (_ completionHandler: @escaping (_ capturedImage: UIImage?) -> Void) {
  106. self.scrollView.swContentScrollCapture(completionHandler)
  107. }
  108. }