# iOS截图汇总 **Repository Path**: BlogDemo/ios_screenshot_summary ## Basic Information - **Project Name**: iOS截图汇总 - **Description**: iOS截图汇总 - **Primary Language**: Swift - **License**: MPL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-01-07 - **Last Updated**: 2021-11-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ### iOS 截图终极汇总 - 系统截图 > 系统截图我们主要是在用户截图以后,我们拿到截图内容可以做一些分享,问题反馈等功能,具体过程如下: 1. 添加系统通知,监听截图事件 ```swift NotificationCenter.default.addObserver(self, selector: #selector(systemSnap(noti:)), name: UIApplication.userDidTakeScreenshotNotification, object: nil) ``` 2. 在通知事件中,我们用上下文获取到用户的截图内容 ```swift func getSnapImage() -> UIImage { var imageSize = CGSize.zero let screenSize = UIScreen.main.bounds.size let orientation = UIApplication.shared.statusBarOrientation if orientation.isPortrait { // 根据屏幕的方向设置图片的宽高 imageSize = screenSize } else { imageSize = CGSize(width: screenSize.height, height: screenSize.width) } // 开启上下文的option, size, 透明度, 屏幕scale UIGraphicsBeginImageContextWithOptions(imageSize, false, UIScreen.main.scale) if let context = UIGraphicsGetCurrentContext() { // 根据屏幕方向旋转图片(可以省略) for window in UIApplication.shared.windows { context.saveGState() context.translateBy(x: window.center.x, y: window.center.y) context.concatenate(window.transform) context.translateBy(x: -window.bounds.size.width * window.layer.anchorPoint.x, y: -window.bounds.size.height * window.layer.anchorPoint.y) if orientation == UIInterfaceOrientation.landscapeLeft { context.rotate(by: .pi / 2) context.translateBy(x: 0, y: -imageSize.width) } else if orientation == UIInterfaceOrientation.landscapeRight { context.rotate(by: -.pi / 2) context.translateBy(x: -imageSize.height, y: 0) } else if orientation == UIInterfaceOrientation.portraitUpsideDown { context.rotate(by: .pi) context.translateBy(x: -imageSize.width, y: -imageSize.height) } if window.responds(to: #selector(UIView.drawHierarchy(in:afterScreenUpdates:))) { window.drawHierarchy(in: window.bounds, afterScreenUpdates: true) } else { window.layer.render(in: context) } context.restoreGState() } } // 获取图片并且关闭上下文 let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() if let image = image { return image } else { return UIImage() } } ``` - 普通 View 的截图 > 对于普通的 View 及 View 的子类,我们可以直接通过上下文来获取图片 ```swift UIGraphicsBeginImageContextWithOptions(self.view.frame.size, true, UIScreen.main.scale) self.view.layer.render(in: UIGraphicsGetCurrentContext()!) let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() ``` - TableView 长截图 > 对于 tableView 的长截图,其实就是对 scrollView 的截图, 我们需要对 tableview 做一些特殊处理, 当用户滑动位置不在顶部时, 我们截图时需要将 tableView 的 contentOffset 先设置为(0, 0), frame 设置为完整大小,完成截图以后再复原 ```swift // 记录原来的位置和frame, 截图是设置为从头开始,完成后复原 let currentOffset = scrollView.contentOffset let currentFrame = scrollView.frame // 设置contentOffset, frame scrollView.contentOffset = CGPoint.zero scrollView.frame = CGRect(x: 0, y: 0, width: scrollView.contentSize.width, height: scrollView.contentSize.height) // 通过上下文获取图片 UIGraphicsBeginImageContextWithOptions(scrollView.contentSize, true, UIScreen.main.scale) scrollView.layer.render(in: UIGraphicsGetCurrentContext()!) let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() // 复原scrollView scrollView.contentOffset = currentOffset scrollView.frame = currentFrame ``` - UIWebView 截图 > UIWebView 截图其实就是对 UIWebView 里的 ScrollView 的截图, 与 TableView 其实一样 - WKWebView 截图 > 在 iOS12 中,苹果已经废弃了 UIWebView,使用 WKWebView 来代替, 当使用跟 UIWebView 一样的截图方式时,发现只能获取到当前屏幕部分的截图, 在查找解决方案时,发现了一个`drawViewHierarchyInRect`的方法, 它的作用是将当前屏幕的内容渲染到当前的上下文, 这就给 WKWebVeiw 的截图提供了一个思路. 我们可以将 WKWebVeiw 分成若干页,把每一页添加到上下文, 最后在一起获取上下文内容来获取图片, 具体代码及注释如下: ```swift func wkwebViewSnap() { let webView = self.wkwebView // 添加一个假的view 盖在最上层 let snapshotView = webView.snapshotView(afterScreenUpdates: true)! snapshotView.frame = webView.frame webView.superview?.addSubview(snapshotView) // 记录webview的状态 let currentOffset = webView.scrollView.contentOffset let currentFrame = webView.frame let currentSuperView = webView.superview let currentIndex = self.view.subviews.firstIndex(of: webView) // 将webview从父视图移除, 添加到一个新的view上,然后 let containerView = UIView(frame: webView.frame) webView.removeFromSuperview() containerView.addSubview(webView) let totalSize = webView.scrollView.contentSize let page = NSInteger(ceil(totalSize.height / containerView.bounds.size.height)) // 设置webview的scrollview 从 contentOffset 和 frame webView.scrollView.contentOffset = CGPoint.zero webView.frame = CGRect(x: 0, y: 0, width: containerView.bounds.size.width, height: webView.scrollView.contentSize.height) // 开始截图 UIGraphicsBeginImageContextWithOptions(totalSize, true, UIScreen.main.scale) // 分页获取 self.splitContentPage(targetView: containerView, webView: self.wkwebView, index: 0, page: page) { let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() // 将webview复位 self.wkwebView.removeFromSuperview() currentSuperView?.insertSubview(self.wkwebView, at: currentIndex ?? 0) self.wkwebView.frame = currentFrame self.wkwebView.scrollView.contentOffset = currentOffset // 删除临时的view snapshotView.removeFromSuperview() self.gotoPreView(image: image ?? UIImage()) } } func splitContentPage(targetView: UIView, webView: WKWebView, index: NSInteger, page: NSInteger, completion: @escaping ()->()) { // 获取当前页的frame let splitFrame = CGRect(x: 0, y: CGFloat(index) * targetView.bounds.height, width: targetView.frame.width, height: targetView.frame.height) var tempFrame = webView.frame // 控制页面移动一页 tempFrame.origin.y = -(CGFloat(index) * targetView.frame.height) webView.frame = tempFrame // 延迟加载 DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: { // 将每一页的页面加载到上下文中 targetView.drawHierarchy(in: splitFrame, afterScreenUpdates: true) if index < page { self.splitContentPage(targetView: targetView, webView: webView, index: index + 1, page: page, completion: completion) } else { completion() } }) } ``` > 在查看 WKWebView 代码时,发现 WKWebView 自己有一个截图的方法, 但是这个方法只能截取当前显示页面的部分, 所以我们还是跟之前的方法一样,先覆盖一层假的 view,然后把 webView 放在一个新的 view 上, 这样就可以截取整个 webView ```swift // 截图配置 let snapConfig = WKSnapshotConfiguration.init() // 设置获取截图的范围 snapConfig.rect = CGRect(origin: CGPoint(x: 0, y: 0), size: webView.scrollView.contentSize) //获取截图 webView.takeSnapshot(with: snapConfig) { (image, error) in // 复原webview self.wkwebView.frame = currentFrame self.wkwebView.scrollView.contentOffset = currentOffset let resultImg = image } ```