Show Filenames Challenge problem


#1

I’m having a problem with this one. I built it from the code in the book and it ran correctly, I thought. The time interval displayed in the bottom left of the parent layer and the images were displayed with the animation. Now that I’ve added the file names to the mix, the names don’t match the image and the time layer displays ‘desert’ every time I run it, instead of the time interval. When I place a breakpoint where I can check the text variable for each loop, it is storing the time interval correctly, however ‘Desert’ is displayed.

I know I’m missing something simple again. I checked the solution previously posted here and even edited my code to match. Then the text of the file names was cut off and I had the same results of mismatched names and ‘desert’ where the time interval should be displayed. As you will see, I went back to my version since the file names aren’t truncated when displayed on each image.

Here is my code:

import Cocoa

class ViewController: NSViewController {

var textLayer: CATextLayer!

var text: String? {
	didSet {
		let font = NSFont.systemFont(ofSize: textLayer.fontSize)
		let attributes = [kCTFontAttributeName : font]
		var size = text?.size(withAttributes: attributes as [NSAttributedStringKey : Any]) ?? CGSize.zero
		// Ensure the size is a whole number
		// I added 5 here because the last portion of 'interval' was being cut off
		size.width = ceil(size.width + 5)
		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
	}
}

var fileName: String? {
	didSet {
		let font = NSFont.systemFont(ofSize: textLayer.fontSize)
		let attributes = [kCTFontAttributeName : font]
		var size
			= fileName?.size(withAttributes: attributes as [NSAttributedStringKey : Any]) ?? CGSize.zero
		// Ensure the size is a whole number
		// I added 5 here because the last portion of 'fileName' was being cut off
		size.width = ceil(size.width + 5)
		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 = fileName
	}
}

//--------------------------------------------------------------------------------------------------------------

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) { (rect) -> Bool in
		image.draw(in: rect)
		return true
	}
	return smallerImage
}

//--------------------------------------------------------------------------------------------------------------

func addImagesFromFolderURL(folderURL: URL) {
	let t0 = Date.timeIntervalSinceReferenceDate
	
	let fileManager = FileManager()
	let directoryEnumerator = fileManager.enumerator(at: folderURL,
							includingPropertiesForKeys: nil,
												options: [],
											errorHandler: nil)!
	var allowedFiles = 10
	
	while let url = directoryEnumerator.nextObject() as? URL {
		// Skip directories
		
		var urlResource: URLResourceValues!
		do {
			urlResource =
				try url.resourceValues(forKeys: [.isDirectoryKey])
		} catch {
			print("error checking whether URL is directory: \(error)")
			continue
		}
		
		guard !urlResource.isDirectory! else { continue }
		
		guard let image = NSImage(contentsOf: url) else { continue }
		
		// find file name and assign to fileName
		fileName = url.deletingPathExtension().lastPathComponent as String
		//print("FileName: \(fileName!)")  // used for testing
		
		// allowedFiles-- error: Unary operator '--' cannot be applied to an operand of type '@lvalue Int'
		allowedFiles = allowedFiles - 1
		
		// displays 11 images
		guard allowedFiles >= 0 else { break }
		
		let thumbImage = thumbImageFromImage(image: image)
		
		presentImage(image: thumbImage)
		
		let t1 = Date.timeIntervalSinceReferenceDate
		let interval = t1 - t0
		text = String(format: "%0.1fs", interval)
	}
}

//--------------------------------------------------------------------------------------------------------------

override func viewDidLoad() {
	super.viewDidLoad()

	// Set view to be layer-hosting
	view.layer = CALayer()
	view.layer?.frame = CGRect(x: 50, y: 50, width: 1000, height: 750)
	view.layer?.bounds = (view.layer?.frame)!
	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)
	
	// Rely on text's didSet to update textLayer's bounds
		text = "Loading..."
	
	let url = URL(fileURLWithPath: "/Library/Desktop Pictures")
	addImagesFromFolderURL(folderURL: url)
}

//--------------------------------------------------------------------------------------------------------------

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: kCAMediaTimingFunctionEaseInEaseOut)
	
	let positionAnimation = CABasicAnimation()
	positionAnimation.fromValue = NSValue(point: center)
	positionAnimation.duration = 1.5
	positionAnimation.timingFunction = timingFunction
	
	let layer = CALayer()
	layer.contents = image
	layer.actions = ["position" : positionAnimation]
	
	// Adding text layer here to add file name to images
	let textContainer = CALayer()
	textContainer.anchorPoint = CGPoint.zero
	textContainer.position = CGPoint(x: 10, y: 10)
	textContainer.zPosition = 100
	textContainer.backgroundColor = NSColor.clear.cgColor
	textContainer.borderColor = NSColor.clear.cgColor
	textContainer.borderWidth = 2
	textContainer.cornerRadius = 15
	textContainer.shadowOpacity = 0.5
	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
	textLayer.string = fileName
	self.textLayer = textLayer
	textContainer.addSublayer(textLayer)
	
	// Rely on text's didSet to update textLayer's bounds
	fileName = "Loading..."
	
	CATransaction.begin()
	view.layer!.addSublayer(layer)
	layer.position = randomPoint
	layer.bounds = imageBounds
	CATransaction.commit()
  }
}

#2

I have figured out how to correctly display interval. My mistake was using the same variable, textLayer, for both filename and interval. I have added another instance of CATextLayer, filenameLayer, to correct that issue. I’m still having the problem of displaying the wrong filename with the image. One thing at a time…

import Cocoa

class ViewController: NSViewController {

var textLayer: CATextLayer!
var filenameLayer: CATextLayer!

var text: String? {
	didSet {
		let font = NSFont.systemFont(ofSize: textLayer.fontSize)
		let attributes = [kCTFontAttributeName : font]
		var size = text?.size(withAttributes: attributes as [NSAttributedStringKey : Any]) ?? CGSize.zero
		// Ensure the size is a whole number
		// I added 5 here because the last portion of 'interval' was being cut off
		size.width = ceil(size.width + 5)
		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
	}
}

var filename: String? {
	didSet {
		let font = NSFont.systemFont(ofSize: filenameLayer.fontSize)
		let attributes = [kCTFontAttributeName : font]
		var size
			= filename?.size(withAttributes: attributes as [NSAttributedStringKey : Any]) ?? CGSize.zero
		// Ensure the size is a whole number
		// I added 5 here because the last portion of 'filename' was being cut off
		size.width = ceil(size.width + 5)
		size.height = ceil(size.height)
		filenameLayer.bounds = CGRect(origin: CGPoint.zero, size: size)
		filenameLayer.superlayer?.bounds = CGRect(x: 0, y: 0,
									width: size.width + 16, height: size.height + 20)
		filenameLayer.string = filename
	}
}

//--------------------------------------------------------------------------------------------------------------

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) { (rect) -> Bool in
		image.draw(in: rect)
		return true
	}
	return smallerImage
}

//--------------------------------------------------------------------------------------------------------------

func addImagesFromFolderURL(folderURL: URL) {
	let t0 = Date.timeIntervalSinceReferenceDate
	
	let fileManager = FileManager()
	let directoryEnumerator = fileManager.enumerator(at: folderURL,
							includingPropertiesForKeys: nil,
												options: [],
											errorHandler: nil)!
	var allowedFiles = 10
	
	while let url = directoryEnumerator.nextObject() as? URL {
		// Skip directories
		
		var urlResource: URLResourceValues!
		do {
			urlResource =
				try url.resourceValues(forKeys: [.isDirectoryKey])
		} catch {
			print("error checking whether URL is directory: \(error)")
			continue
		}
		
		guard !urlResource.isDirectory! else { continue }
		
		guard let image = NSImage(contentsOf: url) else { continue }
		
		// find file name and assign to filename
		filename = url.deletingPathExtension().lastPathComponent as String
		//print("filename: \(filename!)")  // used for testing
		
		// allowedFiles-- error: Unary operator '--' cannot be applied to an operand of type '@lvalue Int'
		allowedFiles = allowedFiles - 1
		
		// displays 11 images
		guard allowedFiles >= 0 else { break }
		
		let thumbImage = thumbImageFromImage(image: image)
		
		presentImage(image: thumbImage)
		
		let t1 = Date.timeIntervalSinceReferenceDate
		let interval = t1 - t0
		text = String(format: "%0.1fs", interval)
	}
}

//--------------------------------------------------------------------------------------------------------------

override func viewDidLoad() {
	super.viewDidLoad()

	// Set view to be layer-hosting
	view.layer = CALayer()
	view.layer?.frame = CGRect(x: 50, y: 50, width: 1000, height: 750)
	view.layer?.bounds = (view.layer?.frame)!
	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)
	
	// Rely on text's didSet to update textLayer's bounds
	text = "Loading..."
	
	// added to avoid unwrapping nil -- fatal error
	let filenameLayer = CATextLayer()
	filenameLayer.string = filename
	self.filenameLayer = filenameLayer
	
	let url = URL(fileURLWithPath: "/Library/Desktop Pictures")
	addImagesFromFolderURL(folderURL: url)
}

//--------------------------------------------------------------------------------------------------------------

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: kCAMediaTimingFunctionEaseInEaseOut)
	
	let positionAnimation = CABasicAnimation()
	positionAnimation.fromValue = NSValue(point: center)
	positionAnimation.duration = 1.5
	positionAnimation.timingFunction = timingFunction
	
	let layer = CALayer()
	layer.contents = image
	layer.actions = ["position" : positionAnimation]
	
	// Adding text layer here to add filename to image
	let textContainer = CALayer()
	textContainer.anchorPoint = CGPoint.zero
	textContainer.position = CGPoint(x: 10, y: 10)
	textContainer.zPosition = 100
	textContainer.backgroundColor = NSColor.clear.cgColor
	textContainer.borderColor = NSColor.clear.cgColor
	textContainer.borderWidth = 2
	textContainer.cornerRadius = 15
	textContainer.shadowOpacity = 0.5
	layer.addSublayer(textContainer)
	
	let filenameLayer = CATextLayer()
	filenameLayer.anchorPoint = CGPoint.zero
	filenameLayer.position = CGPoint(x: 10, y: 6)
	filenameLayer.zPosition = 100
	filenameLayer.fontSize = 24
	filenameLayer.foregroundColor = NSColor.white.cgColor
	filenameLayer.string = filename
	self.filenameLayer = filenameLayer
	
	textContainer.addSublayer(filenameLayer)
	
	// Rely on filename's didSet to update filenameLayer's bounds
	filename = "Loading..."
	
	
	
	CATransaction.begin()
	view.layer!.addSublayer(layer)
	layer.position = randomPoint
	layer.bounds = imageBounds
	CATransaction.commit()
  }
}

#3

Got it…as my wife always says, walk away from it for a while, then go back to it and you can figure it out. In addImagesFromFolderURL() I had to move the following line of code to AFTER presentImage() was called to display the correct names with their images (I know that isn’t the correct syntax for naming a function/method, but this site always wants to make the required format into a smily face) (_:slight_smile: :

// find file name and assign to filename
filename = url.deletingPathExtension().lastPathComponent as String

Corrected code for addImagesFromFolderURL():

func addImagesFromFolderURL(folderURL: URL) {
	let t0 = Date.timeIntervalSinceReferenceDate
	
	let fileManager = FileManager()
	let directoryEnumerator = fileManager.enumerator(at: folderURL,
							includingPropertiesForKeys: nil,
												options: [],
											errorHandler: nil)!
	var allowedFiles = 10
	
	while let url = directoryEnumerator.nextObject() as? URL {
		// Skip directories
		
		var urlResource: URLResourceValues!
		do {
			urlResource =
				try url.resourceValues(forKeys: [.isDirectoryKey])
		} catch {
			print("error checking whether URL is directory: \(error)")
			continue
		}
		
		guard !urlResource.isDirectory! else { continue }
		
		guard let image = NSImage(contentsOf: url) else { continue }
		
		// allowedFiles-- error: Unary operator '--' cannot be applied to an operand of type '@lvalue Int'
		allowedFiles = allowedFiles - 1
		
		// displays 11 images
		guard allowedFiles >= 0 else { break }
		
		let thumbImage = thumbImageFromImage(image: image)
		
		presentImage(image: thumbImage)
		// find file name and assign to filename
		filename = url.deletingPathExtension().lastPathComponent as String
		//print("filename: \(filename!)")  // used for testing
		
		let t1 = Date.timeIntervalSinceReferenceDate
		let interval = t1 - t0
		text = String(format: "%0.1fs", interval)
	}
}