Skip to content

Instantly share code, notes, and snippets.

@pavel-sazonov
Created April 23, 2019 11:33
Show Gist options
  • Save pavel-sazonov/fb8c511b724ec12f382ce9bc2e1a99b6 to your computer and use it in GitHub Desktop.
Save pavel-sazonov/fb8c511b724ec12f382ce9bc2e1a99b6 to your computer and use it in GitHub Desktop.

Revisions

  1. pavel-sazonov created this gist Apr 23, 2019.
    220 changes: 220 additions & 0 deletions Utility.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,220 @@
    //
    // Utilities.swift
    //
    // Created by CS193p Instructor.
    // Copyright © 2017 Stanford University. All rights reserved.
    //

    import UIKit

    class ImageFetcher {
    // Public API

    // To use, create with the closure you want called when the image is ready.
    // Example: let fetcher = ImageFetcher() { // code to execute when fetch is done }
    // Your closure is invoked OFF THE MAIN THREAD.
    // Then call fetch(url:) with the url you want to fetch.
    // And set a backup image in case the fetch fails.
    //
    // The handler will be called immediately if the fetch succeeds.
    // If the fetch fails, the handler will be called if and when the backup image is set.
    // The backup can be set at any time (i.e. before, during or after the fetch).
    // If the fetch fails and a backup image is never set, the handler will never be called.
    // Thus it would sort of be a strange use of this class to not set a backup image
    // (because you'd never find out when the fetch failed).
    // Note that you must keep a strong pointer to this object until the fetch finishes
    // otherwise the result of the fetch will be discarded and the handler never called.
    // In other words, keeping a strong pointer to your instance says "I'm still interested in its result."

    var backup: UIImage? { didSet { callHandlerIfNeeded() } }

    func fetch(_ url: URL) {
    DispatchQueue.global(qos: .userInitiated).async { [weak self] in
    if let data = try? Data(contentsOf: url.imageURL) {
    if self != nil {
    // yes, it's ok to create a UIImage off the main thread
    if let image = UIImage(data: data) {
    self?.handler(url, image)
    } else {
    self?.fetchFailed = true
    }
    } else {
    print("ImageFetcher: fetch returned but I've left the heap -- ignoring result.")
    }
    } else {
    self?.fetchFailed = true
    }
    }
    }

    init(handler: @escaping (URL, UIImage) -> Void) {
    self.handler = handler
    }

    init(fetch url: URL, handler: @escaping (URL, UIImage) -> Void) {
    self.handler = handler
    fetch(url)
    }

    // Private Implementation

    private let handler: (URL, UIImage) -> Void
    private var fetchFailed = false { didSet { callHandlerIfNeeded() } }
    private func callHandlerIfNeeded() {
    if fetchFailed, let image = backup,
    let url = image.storeLocallyAsJPEG(named: String(Date().timeIntervalSinceReferenceDate)) {
    handler(url, image)
    }
    }
    }

    extension URL {
    var imageURL: URL {
    if let url = UIImage.urlToStoreLocallyAsJPEG(named: self.path) {
    // this was created using UIImage.storeLocallyAsJPEG
    return url
    } else {
    // check to see if there is an embedded imgurl reference
    for query in query?.components(separatedBy: "&") ?? [] {
    let queryComponents = query.components(separatedBy: "=")
    if queryComponents.count == 2 {
    if queryComponents[0] == "imgurl",
    let url = URL(string: queryComponents[1].removingPercentEncoding ?? "") {
    return url
    }
    }
    }
    return self.baseURL ?? self
    }
    }
    }

    extension UIImage {
    private static let localImagesDirectory = "UIImage.storeLocallyAsJPEG"

    static func urlToStoreLocallyAsJPEG(named: String) -> URL? {
    var name = named
    let pathComponents = named.components(separatedBy: "/")
    if pathComponents.count > 1 {
    if pathComponents[pathComponents.count-2] == localImagesDirectory {
    name = pathComponents.last!
    } else {
    return nil
    }
    }
    if var url = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first {
    url = url.appendingPathComponent(localImagesDirectory)
    do {
    try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true)
    url = url.appendingPathComponent(name)
    if url.pathExtension != "jpg" {
    url = url.appendingPathExtension("jpg")
    }
    return url
    } catch let error {
    print("UIImage.urlToStoreLocallyAsJPEG \(error)")
    }
    }
    return nil
    }

    func storeLocallyAsJPEG(named name: String) -> URL? {
    // if let imageData = UIImageJPEGRepresentation(self, 1.0) {
    if let imageData = self.jpegData(compressionQuality: 1.0) {
    if let url = UIImage.urlToStoreLocallyAsJPEG(named: name) {
    do {
    try imageData.write(to: url)
    return url
    } catch let error {
    print("UIImage.storeLocallyAsJPEG \(error)")
    }
    }
    }
    return nil
    }
    }

    extension String {
    func madeUnique(withRespectTo otherStrings: [String]) -> String {
    var possiblyUnique = self
    var uniqueNumber = 1
    while otherStrings.contains(possiblyUnique) {
    possiblyUnique = self + " \(uniqueNumber)"
    uniqueNumber += 1
    }
    return possiblyUnique
    }
    }

    extension Array where Element: Equatable {
    var uniquified: [Element] {
    var elements = [Element]()
    forEach { if !elements.contains($0) { elements.append($0) } }
    return elements
    }
    }

    extension NSAttributedString {
    func withFontScaled(by factor: CGFloat) -> NSAttributedString {
    let mutable = NSMutableAttributedString(attributedString: self)
    mutable.setFont(mutable.font?.scaled(by: factor))
    return mutable
    }
    var font: UIFont? {
    get { return attribute(.font, at: 0, effectiveRange: nil) as? UIFont }
    }
    }

    extension String {
    func attributedString(withTextStyle style: UIFont.TextStyle,
    ofSize size: CGFloat) -> NSAttributedString {
    let font = UIFontMetrics(forTextStyle: .body)
    .scaledFont(for: UIFont.preferredFont(forTextStyle: .body)
    .withSize(size))
    return NSAttributedString(string: self, attributes: [.font:font])
    }
    }

    extension NSMutableAttributedString {
    func setFont(_ newValue: UIFont?) {
    if newValue != nil { addAttributes([.font:newValue!], range: NSMakeRange(0, length)) }
    }
    }

    extension UIFont {
    func scaled(by factor: CGFloat) -> UIFont { return withSize(pointSize * factor) }
    }

    extension UILabel {
    func stretchToFit() {
    let oldCenter = center
    sizeToFit()
    center = oldCenter
    }
    }

    extension CGPoint {
    func offset(by delta: CGPoint) -> CGPoint {
    return CGPoint(x: x + delta.x, y: y + delta.y)
    }
    }

    extension UIViewController {
    var contents: UIViewController {
    if let navcon = self as? UINavigationController {
    return navcon.visibleViewController ?? navcon
    } else {
    return self
    }
    }
    }

    extension UIView {
    var snapshot: UIImage? {
    UIGraphicsBeginImageContext(bounds.size)
    drawHierarchy(in: bounds, afterScreenUpdates: true)
    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return image
    }
    }