Ch34. Challenge: An Even Better Scattered


#1

So, tried creating two separate queues in ViewController.swift for improving the speed of loading and viewing images, but it doesn’t seem to be any faster. Any suggestions?

Here’s the code (Swift 4.2):

import Cocoa

class ViewController: NSViewController
{
    let processingQueue: OperationQueue =
    {
        let result = OperationQueue()
//        result.maxConcurrentOperationCount = 4    // no limit on processingQueue.
        return result
    }()
    
    let ioQueue: OperationQueue =
    {
        let result = OperationQueue()
        result.maxConcurrentOperationCount = 4  // limit since io reading can be slow.
        return result
    }()
    
    
    var textLayer: CATextLayer!
    
    var text: String?
    {
        didSet
        {
            let font = NSFont.systemFont(ofSize: textLayer.fontSize)
            let attributes = [NSAttributedString.Key.font : font]
            var size = text?.size(withAttributes: attributes) ?? CGSize.zero
            
            // Ensure that the size is in whole numbers:
            size.width = ceil(size.width)
            size.height = ceil(size.height)
            
            textLayer.bounds = CGRect(origin: CGPoint.zero, size: size)
            textLayer.superlayer!.bounds = CGRect(x: 0, y: 0, width: size.width + 16, height: size.height + 20)
            textLayer.string = text
        }
    }

    
    override func viewDidLoad()
    {
        super.viewDidLoad()

        // Set view to be layer-hosting:
        view.layer = CALayer()
        view.wantsLayer = true
        
        let textContainer = CALayer()
        textContainer.anchorPoint = CGPoint.zero
        textContainer.position = CGPoint(x: 10, y: 10)
        textContainer.zPosition = 100
        textContainer.backgroundColor = NSColor.black.cgColor
        textContainer.borderColor = NSColor.white.cgColor
        textContainer.borderWidth = 2
        textContainer.cornerRadius = 15
        textContainer.shadowOpacity = 0.5
        
        view.layer!.addSublayer(textContainer)
        
        let textLayer = CATextLayer()
        textLayer.anchorPoint = CGPoint.zero
        textLayer.position = CGPoint(x: 10, y: 6)
        textLayer.zPosition = 100
        textLayer.fontSize = 24
        textLayer.foregroundColor = NSColor.white.cgColor
        
        self.textLayer = textLayer
        
        textContainer.addSublayer(textLayer)
        
        // Relay on text's didSet to update textLayer's bounds:
        text = "Loading..."
        
        let url = URL(fileURLWithPath: "/Library/Desktop Pictures", isDirectory: true)
        addImagesFromFolderURL(url)
    }
    
    
    func thumbImageFromImage(_ image: NSImage) -> NSImage
    {
        let targetHeight: CGFloat = 200.0
        let imageSize = image.size
        let smallerSize = NSSize(width: targetHeight + imageSize.width / imageSize.height,
                                 height: targetHeight)
        
        let smallerImage = NSImage(size: smallerSize,
                                   flipped: false,
                                   drawingHandler: { (rect) -> Bool in
                                                    image.draw(in: rect)
                                                    return true
                                                    })
        return smallerImage
    }
    
    
    func addImagesFromFolderURL(_ folderURL: URL)
    {
        let t0 = NSDate.timeIntervalSinceReferenceDate
        
        
        ioQueue.addOperation
        {
            let fileManager = FileManager()
            let directoryEnumerator = fileManager.__enumerator(at: folderURL,
                                                               includingPropertiesForKeys: nil,
                                                               options: [],
                                                               errorHandler: nil)!
            
            while let url = directoryEnumerator.nextObject() as? NSURL
            {
                // Skip directories:
                var isDirectoryValue: AnyObject?
                do
                {
                    try url.getResourceValue(&isDirectoryValue,
                                             forKey: URLResourceKey.isDirectoryKey)
                }
                catch
                {
                    print("error checking whether URL is directory: \(error)")
                    continue
                }
                
                
                self.ioQueue.addOperation
                {
                    guard let isDirectory = isDirectoryValue as? Bool, isDirectory == false
                        else { return }
                    
                    guard let image = NSImage(contentsOf: url as URL)
                        else { return }
                    
                    
                    self.processingQueue.addOperation
                    {
                        let thumbImage = self.thumbImageFromImage(image)
                        
                        
                        OperationQueue.main.addOperation
                            {
                                self.presentImage(thumbImage)
                                let t1 = NSDate.timeIntervalSinceReferenceDate
                                let interval = t1 - t0
                                self.text = String(format: "%0.1fs", interval)
                        }
                            
                    }
                        
                }
            }
        }
        
        
        
    
    }
    
    
    func presentImage(_ image: NSImage)
    {
        let superlayerBounds = view.layer!.bounds
        
        let center = CGPoint(x: superlayerBounds.midX, y: superlayerBounds.midY)
        
        let imageBounds = CGRect(origin: CGPoint.zero, size: image.size)
        
        let randomPoint =
            CGPoint(x: CGFloat(arc4random_uniform(UInt32(superlayerBounds.maxX))),
                    y: CGFloat(arc4random_uniform(UInt32(superlayerBounds.maxY))))
        
        let timingFunction
            = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
        
        let positionAnimation = CABasicAnimation()
        positionAnimation.fromValue = NSValue(point: center)
        positionAnimation.duration = 1.5
        positionAnimation.timingFunction = timingFunction
        
        let boundsAnimation = CABasicAnimation()
        boundsAnimation.fromValue = NSValue(rect: CGRect.zero)
        boundsAnimation.duration = 1.5
        boundsAnimation.timingFunction = timingFunction
        
        let layer = CALayer()
        layer.contents = image
        layer.actions =
            ["position" : positionAnimation,
             "bounds"   : boundsAnimation]
        
        CATransaction.begin()
        view.layer!.addSublayer(layer)
        layer.position = randomPoint
        layer.bounds = imageBounds
        CATransaction.commit()
    }

}