diff --git a/Corona.xcodeproj/project.pbxproj b/Corona.xcodeproj/project.pbxproj index e8b2a78..c646cf2 100644 --- a/Corona.xcodeproj/project.pbxproj +++ b/Corona.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 1114CFC82423F8CC00B1CF85 /* ImageItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1114CFC62423F8CC00B1CF85 /* ImageItemSource.swift */; }; + 1114CFC92423F8CC00B1CF85 /* TextItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1114CFC72423F8CC00B1CF85 /* TextItemSource.swift */; }; 111C76B62412C90500368F08 /* SystemColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111C76B52412C90500368F08 /* SystemColor.swift */; }; 1124F64624224E240002641D /* MenuController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1124F64524224E240002641D /* MenuController.swift */; }; 1124F64A242386A30002641D /* MenuSegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1124F649242386A30002641D /* MenuSegue.swift */; }; @@ -92,6 +94,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 1114CFC62423F8CC00B1CF85 /* ImageItemSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageItemSource.swift; sourceTree = ""; }; + 1114CFC72423F8CC00B1CF85 /* TextItemSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextItemSource.swift; sourceTree = ""; }; 111C76B52412C90500368F08 /* SystemColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SystemColor.swift; sourceTree = ""; }; 1124F64524224E240002641D /* MenuController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuController.swift; sourceTree = ""; }; 1124F649242386A30002641D /* MenuSegue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuSegue.swift; sourceTree = ""; }; @@ -162,12 +166,21 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 1114CFC52423F8CC00B1CF85 /* Share */ = { + isa = PBXGroup; + children = ( + 1114CFC62423F8CC00B1CF85 /* ImageItemSource.swift */, + 1114CFC72423F8CC00B1CF85 /* TextItemSource.swift */, + ); + path = Share; + sourceTree = ""; + }; 1124F64D242394BB0002641D /* Menu */ = { isa = PBXGroup; children = ( - 1124F64524224E240002641D /* MenuController.swift */, - 1124F649242386A30002641D /* MenuSegue.swift */, 1124F64B24238C600002641D /* Menu.swift */, + 1124F649242386A30002641D /* MenuSegue.swift */, + 1124F64524224E240002641D /* MenuController.swift */, ); path = Menu; sourceTree = ""; @@ -226,8 +239,8 @@ isa = PBXGroup; children = ( 1151868D241FAECB00EBC83E /* Data */, - 1151868F241FAECB00EBC83E /* Model */, 11518695241FAECB00EBC83E /* Service */, + 1151868F241FAECB00EBC83E /* Model */, 1162D15F240FA85300647002 /* Map */, 1162D151240F934000647002 /* Chart */, 1124F64D242394BB0002641D /* Menu */, @@ -269,6 +282,7 @@ 1162D16B240FC55F00647002 /* Util */ = { isa = PBXGroup; children = ( + 1114CFC52423F8CC00B1CF85 /* Share */, 1151869B241FAF6200EBC83E /* Extensions.swift */, 1151868A241FA9AA00EBC83E /* UIExtensions.swift */, 111C76B52412C90500368F08 /* SystemColor.swift */, @@ -447,7 +461,9 @@ 1162D145240EB86400647002 /* RegionAnnotation.swift in Sources */, 11EB2C9F2413FFE1001769B2 /* HistoryChartView.swift in Sources */, 115186A0241FAFCA00EBC83E /* TimeSeries.swift in Sources */, + 1114CFC82423F8CC00B1CF85 /* ImageItemSource.swift in Sources */, 1151869C241FAF6200EBC83E /* Extensions.swift in Sources */, + 1114CFC92423F8CC00B1CF85 /* TextItemSource.swift in Sources */, 11EB2CA1241400EF001769B2 /* TopCountriesChartView.swift in Sources */, 3447B4BD241E7790003914AF /* App.swift in Sources */, 115186C024203D5100EBC83E /* Change.swift in Sources */, diff --git a/Corona/Assets.xcassets/Icon-Small.imageset/Contents.json b/Corona/Assets.xcassets/Icon-Small.imageset/Contents.json new file mode 100644 index 0000000..2041046 --- /dev/null +++ b/Corona/Assets.xcassets/Icon-Small.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Icon-Small.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" + } +} \ No newline at end of file diff --git a/Corona/Assets.xcassets/Icon-Small.imageset/Icon-Small.pdf b/Corona/Assets.xcassets/Icon-Small.imageset/Icon-Small.pdf new file mode 100644 index 0000000..3df83b0 Binary files /dev/null and b/Corona/Assets.xcassets/Icon-Small.imageset/Icon-Small.pdf differ diff --git a/Corona/Assets.xcassets/Reload.imageset/Contents.json b/Corona/Assets.xcassets/Reload.imageset/Contents.json new file mode 100644 index 0000000..7bcc985 --- /dev/null +++ b/Corona/Assets.xcassets/Reload.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Reload.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" + } +} \ No newline at end of file diff --git a/Corona/Assets.xcassets/Reload.imageset/Reload.pdf b/Corona/Assets.xcassets/Reload.imageset/Reload.pdf new file mode 100644 index 0000000..d6190a5 Binary files /dev/null and b/Corona/Assets.xcassets/Reload.imageset/Reload.pdf differ diff --git a/Corona/Assets.xcassets/Share-Circle.imageset/Contents.json b/Corona/Assets.xcassets/Share-Circle.imageset/Contents.json new file mode 100644 index 0000000..102b74d --- /dev/null +++ b/Corona/Assets.xcassets/Share-Circle.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Share-Circle.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Corona/Assets.xcassets/Share-Circle.imageset/Share-Circle.pdf b/Corona/Assets.xcassets/Share-Circle.imageset/Share-Circle.pdf new file mode 100644 index 0000000..21f9eeb Binary files /dev/null and b/Corona/Assets.xcassets/Share-Circle.imageset/Share-Circle.pdf differ diff --git a/Corona/Assets.xcassets/Share.imageset/Contents.json b/Corona/Assets.xcassets/Share.imageset/Contents.json new file mode 100644 index 0000000..ac0f923 --- /dev/null +++ b/Corona/Assets.xcassets/Share.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Share.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" + } +} \ No newline at end of file diff --git a/Corona/Assets.xcassets/Share.imageset/Share.pdf b/Corona/Assets.xcassets/Share.imageset/Share.pdf new file mode 100644 index 0000000..80409c4 Binary files /dev/null and b/Corona/Assets.xcassets/Share.imageset/Share.pdf differ diff --git a/Corona/Base.lproj/Main.storyboard b/Corona/Base.lproj/Main.storyboard index 8995cc4..42be317 100644 --- a/Corona/Base.lproj/Main.storyboard +++ b/Corona/Base.lproj/Main.storyboard @@ -105,10 +105,10 @@ - + - + @@ -198,6 +198,7 @@ + @@ -205,11 +206,16 @@ + + + + + - + - + @@ -226,8 +232,13 @@ + + + + + - + @@ -247,8 +258,13 @@ + + + + + - + @@ -281,6 +297,11 @@ + + + + + @@ -364,7 +385,7 @@ - + @@ -553,81 +574,93 @@ Deaths - - - + + + + + - - + + + + + + + + + + + + - - - - - - - - + - - - - - - - - - + + - + - - + @@ -639,6 +672,7 @@ Deaths + diff --git a/Corona/Controller/RegionContainerController.swift b/Corona/Controller/RegionContainerController.swift index 160a9f0..e562d52 100644 --- a/Corona/Controller/RegionContainerController.swift +++ b/Corona/Controller/RegionContainerController.swift @@ -9,6 +9,18 @@ import UIKit class RegionContainerController: UIViewController { + private lazy var buttonDone: UIButton = { + let button = UIButton(type: .system) + button.titleLabel?.font = .boldSystemFont(ofSize: 17) + button.setTitle("Done", for: .normal) + button.addTarget(self, action: #selector(buttonDoneTapped(_:)), for: .touchUpInside) + viewHeader.addSubview(button) + button.translatesAutoresizingMaskIntoConstraints = false + button.trailingAnchor.constraint(equalTo: viewHeader.trailingAnchor, constant: -18).isActive = true + button.centerYAnchor.constraint(equalTo: searchBar.centerYAnchor).isActive = true + return button + }() + var regionListController: RegionListController! var regionController: RegionController! var isUpdating: Bool = false { @@ -43,6 +55,7 @@ class RegionContainerController: UIViewController { @IBOutlet var effectViewBackground: UIVisualEffectView! @IBOutlet var effectViewHeader: UIVisualEffectView! + @IBOutlet var viewHeader: UIView! @IBOutlet var labelTitle: UILabel! @IBOutlet var labelUpdated: UILabel! @IBOutlet var buttonMenu: UIButton! @@ -83,10 +96,20 @@ class RegionContainerController: UIViewController { } } + override func setEditing(_ editing: Bool, animated: Bool) { + super.setEditing(editing, animated: animated) + + viewHeader.transition(duration: 0.25) { + self.buttonDone.isHidden = !editing + self.buttonMenu.isHidden = editing + self.buttonSearch.isHidden = editing + } + } + func update(region: Region?) { - UIView.transition(with: view, duration: 0.25, options: [.transitionCrossDissolve], animations: { + viewHeader.transition(duration: 0.25) { self.labelTitle.text = region?.longName ?? "N/A" - }, completion: nil) + } updateTime() } @@ -100,17 +123,39 @@ class RegionContainerController: UIViewController { self.labelUpdated.text = self.regionController.region?.report?.lastUpdate.relativeTimeString } + func snapshotHeader(hideTitle: Bool = false) -> UIImage { + if hideTitle { + labelTitle.isHidden = true + } + buttonDone.isHidden = true + let image = viewHeader.snapshot() + labelTitle.isHidden = false + + return image + } +} + +extension RegionContainerController { @IBAction func buttonSearchTapped(_ sender: Any) { isSearching = true } @IBAction func buttonMenuTapped(_ sender: Any) { Menu.show(above: self, sourceView: buttonMenu, items: [ - MenuItem(title: "Update", image: UIImage(named: "Search")!, action: { + MenuItem(title: "Update", image: UIImage(named: "Reload")!, action: { MapController.instance.downloadIfNeeded() }), + MenuItem(title: "Share", image: UIImage(named: "Share")!, action: { + MapController.instance.showRegionScreen() + self.regionController.setEditing(true, animated: true) + }), ]) } + + @objc func buttonDoneTapped(_ sender: Any) { + setEditing(false, animated: true) + regionController.setEditing(false, animated: true) + } } extension RegionContainerController: UISearchBarDelegate, UITableViewDelegate { diff --git a/Corona/Controller/RegionController.swift b/Corona/Controller/RegionController.swift index c1a62a5..94bc1b9 100644 --- a/Corona/Controller/RegionController.swift +++ b/Corona/Controller/RegionController.swift @@ -23,6 +23,7 @@ class RegionController: UITableViewController { } private var showPercents = false private var switchPercentsTask: DispatchWorkItem? + private var container: RegionContainerController? { parent as? RegionContainerController } @IBOutlet var stackViewStats: UIStackView! @IBOutlet var labelTitle: UILabel! @@ -134,8 +135,43 @@ class RegionController: UITableViewController { } func updateParent() { - (parent as? RegionContainerController)?.update(region: region) + container?.update(region: region) } + + private func shareImage(for cell: RegionInfoCell?) { + guard let cell = cell, let row = cell.row else { return } + + let cellImage = cell.snapshot() + let headerImage = container!.snapshotHeader(hideTitle: row == .chartTop) + var logoImage = UIImage(named: "Icon-Small") + if #available(iOS 13.0, *) { + logoImage = logoImage?.withTintColor(SystemColor.secondaryLabel) + } + + let newSize = CGSize(width: cellImage.size.width, height: cellImage.size.height + headerImage.size.height) + let newBounds = CGRect(origin: .zero, size: newSize) + let image = UIGraphicsImageRenderer(bounds: newBounds).image { rendererContext in + SystemColor.secondarySystemBackground.setFill() + rendererContext.fill(newBounds) + + headerImage.draw(at: .zero) + logoImage?.draw(at: .init(x: headerImage.size.width - 60, y: 22)) + cellImage.draw(at: .init(x: 0, y: headerImage.size.height)) + } + + var items: [Any] = [ImageItemSource(image: image, imageName: "Corona Tracker")] + items.append(TextItemSource(text: row.title)) + + let activityController = UIActivityViewController(activityItems: items, applicationActivities: nil) + + if UIDevice.current.userInterfaceIdiom == .pad { + activityController.modalPresentationStyle = .popover + activityController.popoverPresentationController?.sourceView = cell + activityController.popoverPresentationController?.sourceRect = cell.bounds + } + present(activityController, animated: true, completion: nil) + } + } extension RegionController { @@ -157,3 +193,114 @@ extension RegionController { present(safariController, animated: true) } } + +extension RegionController { + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = super.tableView(tableView, cellForRowAt: indexPath) + if let cell = cell as? RegionInfoCell { + cell.shareAction = { + self.setEditing(false, animated: true) + self.shareImage(for: cell) + } + } + return cell + } + + override func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool { + false + } + + override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle { + .none + } + + @available(iOS 11.0, *) + override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + let cell = tableView.cellForRow(at: indexPath) as? RegionInfoCell + let action = UIContextualAction(style: .normal, title: nil) { action, sourceView, completion in + completion(true) + self.shareImage(for: cell) + } + action.image = UIImage(named: "Share-Circle") + action.backgroundColor = UIColor.black.withAlphaComponent(0.001) + + let config = UISwipeActionsConfiguration(actions: [action]) + config.performsFirstActionWithFullSwipe = false + return config + } + + override func setEditing(_ editing: Bool, animated: Bool) { + super.setEditing(editing, animated: animated) + + container?.setEditing(editing, animated: animated) + } +} + +class RegionInfoCell: UITableViewCell { + enum Row: Int, RawRepresentable { + case stats = 1 + case chartCurrent = 2 + case chartHistory = 3 + case chartTop = 4 + + var title: String { + switch self { + case .stats: return "Coronavirus live update (via CoronaTracker)" + case .chartCurrent: return "Coronavirus live update (via CoronaTracker)" + case .chartHistory: return "Coronavirus growth chart (via CoronaTracker)" + case .chartTop: return "Top affected countries (via CoronaTracker)" + } + } + } + + private lazy var buttonShare: UIButton = { + let button = UIButton(type: .custom) + button.setImage(UIImage(named: "Share-Circle"), for: .normal) + button.translatesAutoresizingMaskIntoConstraints = false + button.widthAnchor.constraint(equalToConstant: 50).isActive = true + button.heightAnchor.constraint(equalToConstant: 50).isActive = true + button.transform = .init(scaleX: 0.1, y: 0.1) + button.alpha = 0 + button.addAction { + self.shareAction?() + } + return button + }() + var shareAction: (() -> Void)? = nil + + @IBInspectable var rowNumber: Int = 0 + var row: Row? { Row(rawValue: rowNumber) } + + override func awakeFromNib() { + super.awakeFromNib() + + clipsToBounds = false + contentView.clipsToBounds = false + + contentView.addSubview(buttonShare) + buttonShare.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true + buttonShare.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -15).isActive = true + } + + override func setEditing(_ editing: Bool, animated: Bool) { + super.setEditing(editing, animated: animated) + + guard superview is UITableView else { return } + + UIView.animate(withDuration: editing ? 0.5 : 0.25, + delay: 0, + usingSpringWithDamping: editing ? 0.7 : 2, + initialSpringVelocity: 0, + options: [], + animations: { + + let scale: CGFloat = editing ? 1 : 0.1 + let alpha: CGFloat = editing ? 1 : 0 + self.buttonShare.transform = .init(scaleX: scale, y: scale) + self.buttonShare.alpha = alpha + self.contentView.subviews.filter({ $0 !== self.buttonShare }).forEach { subview in + subview.transform = editing ? .init(translationX: -self.buttonShare.bounds.width - 15, y: 0) : .identity + } + }) + } +} diff --git a/Corona/Info.plist b/Corona/Info.plist index e6debd2..785bb01 100644 --- a/Corona/Info.plist +++ b/Corona/Info.plist @@ -62,5 +62,7 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + NSPhotoLibraryAddUsageDescription + The app needs permission to save the shared image on your device. diff --git a/Corona/Service/JHUWebDataService.swift b/Corona/Service/JHUWebDataService.swift index 345009e..99032fd 100644 --- a/Corona/Service/JHUWebDataService.swift +++ b/Corona/Service/JHUWebDataService.swift @@ -20,14 +20,14 @@ public class JHUWebDataService: DataService { private static let reportsFileName = "JHUWebDataService-Reports.json" private static let globalTimeSeriesFileName = "JHUWebDataService-GlobalTimeSeries.json" - private static let reportsURL = URL(string: "https://services1.arcgis.com/0MSEUqKaxRlEPj5g/arcgis/rest/services/ncov_cases/FeatureServer/1/query?f=json&where=Confirmed%20%3E%200&returnGeometry=false&spatialRel=esriSpatialRelIntersects&outFields=*&orderByFields=Confirmed%20desc%2CCountry_Region%20asc%2CProvince_State%20asc&resultOffset=0&resultRecordCount=500&cacheHint=true")! + private static var reportsURL: URL { URL(string: "https://services1.arcgis.com/0MSEUqKaxRlEPj5g/arcgis/rest/services/ncov_cases/FeatureServer/1/query?f=json&where=Confirmed%20%3E%200&returnGeometry=false&spatialRel=esriSpatialRelIntersects&outFields=*&orderByFields=Confirmed%20desc%2CCountry_Region%20asc%2CProvince_State%20asc&resultOffset=0&resultRecordCount=500&cacheHint=false&rnd=\(Int.random())")! + } private static let globalTimeSeriesURL = URL(string: "https://services1.arcgis.com/0MSEUqKaxRlEPj5g/arcgis/rest/services/cases_time_v3/FeatureServer/0/query?f=json&where=1%3D1&returnGeometry=false&spatialRel=esriSpatialRelIntersects&outFields=*&orderByFields=Report_Date_String%20asc&outSR=102100&resultOffset=0&resultRecordCount=2000&cacheHint=true")! static let instance = JHUWebDataService() public func fetchReports(completion: @escaping FetchResultBlock) { print("Calling API") - URLCache.shared.removeAllCachedResponses() let request = URLRequest(url: Self.reportsURL, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData) _ = URLSession.shared.dataTask(with: request) { (data, response, error) in guard let response = response as? HTTPURLResponse, @@ -70,7 +70,6 @@ public class JHUWebDataService: DataService { public func fetchTimeSerieses(completion: @escaping FetchResultBlock) { print("Calling API") - URLCache.shared.removeAllCachedResponses() let request = URLRequest(url: Self.globalTimeSeriesURL, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData) _ = URLSession.shared.dataTask(with: request) { (data, response, error) in guard let response = response as? HTTPURLResponse, diff --git a/Corona/Util/Extensions.swift b/Corona/Util/Extensions.swift index 25ff935..3af873a 100644 --- a/Corona/Util/Extensions.swift +++ b/Corona/Util/Extensions.swift @@ -114,6 +114,8 @@ extension Int { public var groupingFormatted: String { NumberFormatter.groupingFormatter.string(from: NSNumber(value: self))! } + + public static func random() -> Int { random(in: 1.. Any { + if let imageURL = imageURL { + return imageURL + } + + return image + } + + func activityViewController (_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? { + if activityType == .message || + activityType?.rawValue.starts(with: "com.skype.skype.") == true { + return image.pngData() + } + + if let imageURL = imageURL { + return imageURL + } + + return image + } + + func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String { + imageName + } + + func activityViewController(_ activityViewController: UIActivityViewController, thumbnailImageForActivityType activityType: UIActivity.ActivityType?, suggestedSize size: CGSize) -> UIImage? { + image.scaledToAspectFit(size: size) + } + + func activityViewController(_ activityViewController: UIActivityViewController, dataTypeIdentifierForActivityType activityType: UIActivity.ActivityType?) -> String { + String(kUTTypePNG) + } +} diff --git a/Corona/Util/Share/TextItemSource.swift b/Corona/Util/Share/TextItemSource.swift new file mode 100644 index 0000000..ff151df --- /dev/null +++ b/Corona/Util/Share/TextItemSource.swift @@ -0,0 +1,43 @@ +// +// TextItemSource.swift +// Corona Tracker +// +// Created by Mohammad on 3/19/20. +// Copyright © 2020 Samabox. All rights reserved. +// + +import UIKit +import MobileCoreServices + +class TextItemSource: NSObject, UIActivityItemSource { + private let text: String + + init(text: String) { + self.text = text + super.init() + } + + func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any { + text + } + + func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? { + if activityType?.rawValue.starts(with: "net.whatsapp.WhatsApp.") == true { + return nil + } + + return text + } + + func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String { + text + } + + func activityViewController(_ activityViewController: UIActivityViewController, thumbnailImageForActivityType activityType: UIActivity.ActivityType?, suggestedSize size: CGSize) -> UIImage? { + nil + } + + func activityViewController(_ activityViewController: UIActivityViewController, dataTypeIdentifierForActivityType activityType: UIActivity.ActivityType?) -> String { + String(kUTTypePlainText) + } +} diff --git a/Corona/Util/UIExtensions.swift b/Corona/Util/UIExtensions.swift index 4f0ab5f..8c2c05c 100644 --- a/Corona/Util/UIExtensions.swift +++ b/Corona/Util/UIExtensions.swift @@ -57,6 +57,10 @@ extension UIView { completion: nil) } + public func snapshot() -> UIImage { + UIGraphicsImageRenderer(bounds: bounds).image { layer.render(in: $0.cgContext) } + } + public func snapEdgesToSuperview() { snapEdges(to: superview!) } @@ -106,3 +110,64 @@ extension UIViewController { } } } + +extension UIImage { + enum ImageType: String { + case jpeg = "jpg" + case png = "png" + } + + func saveToFile(fileName: String = "image", imageType: ImageType = .jpeg) -> URL? { + guard let directoryURL = FileManager.cachesDirectoryURL else { return nil } + + let imageURL = directoryURL.appendingPathComponent("\(fileName).\(imageType.rawValue)") + print(imageURL) + + var data: Data? + + switch imageType { + case .jpeg: + data = self.jpegData(compressionQuality: 1) + case .png: + data = self.pngData() + } + + guard let imageData = data else { return nil } + + do { + try imageData.write(to: imageURL) + } catch { + return nil + } + + return imageURL + } + + func scaledToAspectFit(size: CGSize) -> UIImage { + let imageSize = self.size + let imageAspectRatio = imageSize.width / imageSize.height + let canvasAspectRatio = size.width / size.height + + var resizeFactor: CGFloat + + if imageAspectRatio > canvasAspectRatio { + resizeFactor = size.width / imageSize.width + } else { + resizeFactor = size.height / imageSize.height + } + + if resizeFactor > 1 { + return self + } + + let scaledSize = CGSize(width: imageSize.width * resizeFactor, height: imageSize.height * resizeFactor) + + UIGraphicsBeginImageContextWithOptions(scaledSize, false, 0) + draw(in: CGRect(origin: .zero, size: scaledSize)) + + let scaledImage = UIGraphicsGetImageFromCurrentImageContext() ?? self + UIGraphicsEndImageContext() + + return scaledImage + } +}