NSDocument Never Saves Document - swift

Life was fairly easy when I first test-developed a text-based application with NSDocument. Now, I have a lot more complicated document-based desktop application with several custom models other than a string with NSTextView. My subclass of NSDocument is the following.
import Cocoa
class Document: NSDocument {
// MARK: - Variables
var image = NSImage()
var myPasteModels = [PasteModel]()
var myPanModel: PanModel?
var myWinModel: WindowModel?
// MARK: - Initialization
override init() {
super.init()
}
// MARK: - Auto saving
override class var autosavesInPlace: Bool {
return false
}
override func data(ofType typeName: String) throws -> Data {
if let viewController = windowControllers[0].contentViewController as? MainViewController {
if viewController.imageModels.count > 0 {
viewController.saveSubViewPositions()
if let window = viewController.view.window {
var pasteModels = [PasteModel]()
for i in 0..<viewController.imageModels.count {
let imageModel = viewController.imageModels[i]
...
...
}
NSKeyedArchiver.setClassName("ColorModel", for: ColorModel.self)
NSKeyedArchiver.setClassName("TextModel", for: TextModel.self)
NSKeyedArchiver.setClassName("ShapeModel", for: ShapeModel.self)
NSKeyedArchiver.setClassName("ShadeModel", for: ShadeModel.self)
NSKeyedArchiver.setClassName("LineModel", for: LineModel.self)
NSKeyedArchiver.setClassName("GradientModel", for: GradientModel.self)
NSKeyedArchiver.setClassName("ArrowModel", for: ArrowModel.self)
NSKeyedArchiver.setClassName("PasteModel", for: PasteModel.self)
NSKeyedArchiver.setClassName("PanModel", for: PanModel.self)
NSKeyedArchiver.setClassName("WindowModel", for: WindowModel.self)
let panModel = PanModel(frameWidth: viewController.panView.frame.size.width, frameHeight: viewController.panView.frame.size.height)
let winModel = WindowModel(width: window.frame.width, height: window.frame.height)
let dict = ["PasteModel": pasteModels, "PanModel": panModel, "WindowModel": winModel] as [String : Any]
do {
let modelData = try NSKeyedArchiver.archivedData(withRootObject: dict, requiringSecureCoding: false)
return modelData
} catch let error as NSError {
Swift.print("\(error)")
}
}
}
}
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
}
override func save(withDelegate delegate: Any?, didSave didSaveSelector: Selector?, contextInfo: UnsafeMutableRawPointer?) {
if let _ = fileURL {
Swift.print("Saved!!!")
} else {
Swift.print("Not saved yet...")
NSApp.sendAction(#selector(NSDocument.saveAs(_:)), to: nil, from: self)
}
}
override func prepareSavePanel(_ savePanel: NSSavePanel) -> Bool {
savePanel.allowedFileTypes = ["fss"]
savePanel.allowsOtherFileTypes = true
savePanel.isExtensionHidden = false
return true
}
}
The problem that I have is that the application never saves a document if I choose Save As under File (or press Command + Shift + S). If I choose Save As, the application goes beep and dismiss the command selection. It does enter the prepareSavePanel method if I set a break point there. So what can I do to go any further? Thanks.

Related

delete boolean value at specific point in set

In my swift code below the goal is to delete the first item in the core data boolean attribute. My code is not working below but its close to what I used to successfully fetch a boolean value. I don't really know what to add. There is a base class and a helper class.
class ViewController: UIViewController {
func deleteBool(imageNo:Int) {
// first check the array bounds
let info = helpBool.shareInstance.fetchBool()
if info.count > imageNo {
}
}
override func viewDidLoad() {
super.viewDidLoad()
deleteBool(imageNo: 1)}}
HELPER CLASS
class helpBool: UIViewController{
static let shareInstance = helpBool()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
func deleteBool(_ boolean: Bool) {
let imageInstance = OnOff(context: context)
imageInstance.bool = boolean
do {
try context.delete()
print("text is saved")
} catch {
print(error.localizedDescription)
}
}

How do I pass a scanned barcode ID from first view controller to second View Controller's UILabel?

This is the barcode scanning tutorial I used in my program, so that you have a lot more context when you read my code: Link
Here is what my program does so far: Essentially, when I scan an item's barcode with my phone, the UIAlert pops up with the barcode ID displayed and a button prompting the user to open the "Results" page. This is all fine and good, but how do I pass that same scanned barcode ID into a label on the Result's page? I have been stuck on this for 2 days now, even though it seems like such an easy task.
Any help is much appreciated <3
Here is my relevant code:
ProductCatalog.plist ->
Link to Image
Scanner_ViewController.swift (first View Controller) ->
import UIKit
import AVFoundation
class Scanner_ViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate, ScannerDelegate
{
private var scanner: Scanner?
override func viewDidLoad()
{
super.viewDidLoad()
self.scanner = Scanner(withDelegate: self)
guard let scanner = self.scanner else
{
return
}
scanner.requestCaptureSessionStartRunning()
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// Mark - AVFoundation delegate methods
public func metadataOutput(_ output: AVCaptureMetadataOutput,
didOutput metadataObjects: [AVMetadataObject],
from connection: AVCaptureConnection)
{
guard let scanner = self.scanner else
{
return
}
scanner.metadataOutput(output,
didOutput: metadataObjects,
from: connection)
}
// Mark - Scanner delegate methods
func cameraView() -> UIView
{
return self.view
}
func delegateViewController() -> UIViewController
{
return self
}
func scanCompleted(withCode code: String)
{
print(code)
showAlert_Success(withTitle: (code))
}
private func showAlert_Success(withTitle title: String)
{
let alertController = UIAlertController(title: title, message: "Product has been successfully scanned", preferredStyle: .alert)
// programatically segue to the next view controller when the UIAlert pops up
alertController.addAction(UIAlertAction(title:"Get Results", style: .default, handler:{ action in self.performSegue(withIdentifier: "toAnalysisPage", sender: self) }))
present(alertController, animated: true)
}
}
Scanner.Swift (accompanies Scanner_ViewController.swift)->
import Foundation
import UIKit
import AVFoundation
protocol ScannerDelegate: class
{
func cameraView() -> UIView
func delegateViewController() -> UIViewController
func scanCompleted(withCode code: String)
}
class Scanner: NSObject
{
public weak var delegate: ScannerDelegate?
private var captureSession : AVCaptureSession?
init(withDelegate delegate: ScannerDelegate)
{
self.delegate = delegate
super.init()
self.scannerSetup()
}
private func scannerSetup()
{
guard let captureSession = self.createCaptureSession()
else
{
return
}
self.captureSession = captureSession
guard let delegate = self.delegate
else
{
return
}
let cameraView = delegate.cameraView()
let previewLayer = self.createPreviewLayer(withCaptureSession: captureSession,
view: cameraView)
cameraView.layer.addSublayer(previewLayer)
}
private func createCaptureSession() -> AVCaptureSession?
{
do
{
let captureSession = AVCaptureSession()
guard let captureDevice = AVCaptureDevice.default(for: .video) else
{
return nil
}
let deviceInput = try AVCaptureDeviceInput(device: captureDevice)
let metaDataOutput = AVCaptureMetadataOutput()
// add device input
if captureSession.canAddInput(deviceInput) && captureSession.canAddOutput(metaDataOutput)
{
captureSession.addInput(deviceInput)
captureSession.addOutput(metaDataOutput)
guard let delegate = self.delegate,
let viewController = delegate.delegateViewController() as? AVCaptureMetadataOutputObjectsDelegate else
{
return nil
}
metaDataOutput.setMetadataObjectsDelegate(viewController,
queue: DispatchQueue.main)
metaDataOutput.metadataObjectTypes = self.metaObjectTypes()
return captureSession
}
}
catch
{
// handle error
}
return nil
}
private func createPreviewLayer(withCaptureSession captureSession: AVCaptureSession,
view: UIView) -> AVCaptureVideoPreviewLayer
{
let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.frame = view.layer.bounds
previewLayer.videoGravity = .resizeAspectFill
return previewLayer
}
private func metaObjectTypes() -> [AVMetadataObject.ObjectType]
{
return [.qr,
.code128,
.code39,
.code39Mod43,
.code93,
.ean13,
.ean8,
.interleaved2of5,
.itf14,
.pdf417,
.upce
]
}
public func metadataOutput(_ output: AVCaptureMetadataOutput,
didOutput metadataObjects: [AVMetadataObject],
from connection: AVCaptureConnection)
{
self.requestCaptureSessionStopRunning()
guard let metadataObject = metadataObjects.first,
let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject,
let scannedValue = readableObject.stringValue,
let delegate = self.delegate
else
{
return
}
delegate.scanCompleted(withCode: scannedValue)
}
public func requestCaptureSessionStartRunning()
{
self.toggleCaptureSessionRunningState()
}
public func requestCaptureSessionStopRunning()
{
self.toggleCaptureSessionRunningState()
}
private func toggleCaptureSessionRunningState()
{
guard let captureSession = self.captureSession
else
{
return
}
if !captureSession.isRunning
{
captureSession.startRunning()
}
else
{
captureSession.stopRunning()
}
}
}
Analysis_ViewController.swift (second view controller) ->
Right now, the forKey: has been hard-coded to item ID 8710908501708 because I have no idea how to actually pass camera-scanned ID's into the second View Controller :/
import UIKit
class Analysis_ViewController: UIViewController
{
#IBOutlet weak var productTitle: UILabel!
func getData()
{
let path = Bundle.main.path(forResource:"ProductCatalog", ofType: "plist")
let dict:NSDictionary = NSDictionary(contentsOfFile: path!)!
if (dict.object(forKey: "8710908501708" as Any) != nil)
{
if let levelDict:[String : Any] = dict.object(forKey: "8710908501708" as Any) as? [String : Any]
{
// use a for loop to iterate through all the keys and values in side the "Levels" dictionary
for (key, value) in levelDict
{
// if we find a key named whatever we care about, we can print out the value
if (key == "name")
{
productTitle.text = (value as! String)
}
}
}
}
}
// listing the better options that are safer in comparison to the scanned product image
override func viewDidLoad()
{
super.viewDidLoad()
getData()
}
}
Do you have a variable to hold the scanned ID in your view controllers? If not, you can add var itemID: String? to both Scanner_ViewController and Analysis_ViewController.
Then in your func where you get the scanned code, you can set it to the variable.
func scanCompleted(withCode code: String) {
print(code)
itemID = code // Saves the scanned code to your var
showAlert_Success(withTitle: (code))
}
For passing data to another view controller via segue, you might want to look into this UIViewController method for segues: documentation here. This answer also might help.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toAnalysisPage" {
if let viewController = segue.destination as? Analysis_ViewController {
viewController.itemID = itemID
}
}
}

delegate method does not get called second time

I am building a simple currency converter app. When ViewController gets opened it calls a function from CoinManager.swift:
class ViewController: UIViewController {
var coinManager = CoinManager()
override func viewDidLoad() {
super.viewDidLoad()
coinManager.delegate = self
coinManager.getCoinPrice(for: "AUD", "AZN", firstCall: true)
}
...
}
CoinManager.swift:
protocol CoinManagerDelegate {
func didUpdatePrice(price1: Double, currency1: String, price2: Double, currency2: String)
func tellTableView(descriptions: [String], symbols: [String])
func didFailWithError(error: Error)
}
struct CoinManager {
var delegate: CoinManagerDelegate?
let baseURL = "https://www.cbr-xml-daily.ru/daily_json.js"
func getCoinPrice (for currency1: String,_ currency2: String, firstCall: Bool) {
if let url = URL(string: baseURL) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
self.delegate?.didFailWithError(error: error!)
return
}
if let safeData = data {
if let coinData = self.parseJSON(safeData) {
if firstCall {
var descriptions = [""]
let listOfCoins = Array(coinData.keys)
for key in listOfCoins {
descriptions.append(coinData[key]!.Name)
}
descriptions.removeFirst()
self.delegate?.tellTableView(descriptions: descriptions, symbols: listOfCoins)
}
if let coinInfo1 = coinData[currency1] {
let value1 = coinInfo1.Value
if let coinInfo2 = coinData[currency2] {
let value2 = coinInfo2.Value
//this line does not do anything the second time I call getCoinPrice:
self.delegate?.didUpdatePrice(price1: value1, currency1: currency1, price2: value2, currency2: currency2)
//And this one does work
print("delegate:\(currency1)")
} else {
print("no name matches currency2")
}
} else {
print("no name matches currency1")
}
}
}
}
task.resume()
}
}
func ParseJSON....
}
The method it calls (ViewController.swift):
extension ViewController: CoinManagerDelegate {
func didUpdatePrice(price1: Double, currency1: String, price2: Double, currency2: String) {
print("didUpdatePrice called")
DispatchQueue.main.async {
let price1AsString = String(price1)
let price2AsString = String(price2)
self.leftTextField.text = price1AsString
self.rightTextField.text = price2AsString
self.leftLabel.text = currency1
self.rightLabel.text = currency2
}
}
...
}
and finally, CurrencyViewController.swift:
var coinManager = CoinManager()
#IBAction func backButtonPressed(_ sender: UIBarButtonItem) {
dismiss(animated: true, completion: nil)
coinManager.getCoinPrice(for: "USD", "AZN", firstCall: false)
}
So when I launch the app i get following in my debug console:
didUpdatePrice called
delegate:AUD
And when I call getCoinPrice() from CurrencyViewController the delegate method does not get called. I know that my code goes through the delegate function line as I get this in debug console:
delegate:USD
I just can't wrap my head around it. The delegate method does not work when gets called second time. Even though it is called by the same algorithm
It's because you're creating a new object of CoinManager in CurrencyViewController where the delegate is not set. So you've to set the delegate every time you create a new instance of CoinManager.
#IBAction func backButtonPressed(_ sender: UIBarButtonItem) {
dismiss(animated: true, completion: nil)
coinManager.delegate = self
coinManager.getCoinPrice(for: "USD", "AZN", firstCall: false)
}
Update: So, the above solution would require for you to make the delegate conformance in CurrencyViewController. If you're looking for an alternate solution you should probably pass the instance of coinManager in ViewController to CurrencyViewController. For that here are the things you need to update.
In CurrencyViewController:
class CurrencyViewController: UIViewController {
var coinManager: CoinManager! // you can optional unwrap if you intent to use CurrencyViewController without coinManager
//...
And in ViewController:
currencyViewController.coinManager = coinManager // passing the instance of coinManager
Can you share the full code of CoinManager? I see this part
if firstCall {
...
}
Maybe some block logic here or unhandled cases? And can you share the full code of protocol?
Also try to print something before this code:
if error != nil {
self.delegate?.didFailWithError(error: error!)
return
}

Why is my UIViewController not showing up in my popup card?

I wanted to create a pop up for one of my UIViewController and found this repo on GitHub.
It is working fine with my InfoViewController which only has 4 UILabels (I think this might be the problem that it is not showing up when you use reusable cells)
But somehow it is not working with my StructureNavigationListViewController and I do not know why.
I call the didTapCategory method in my MainViewController where the StructureNavigationController should pop up but I only see the dimming view (which is weird cause the tap recognizer and pan gestures are working fine but no content is showing up)
In my MainViewController I set up the popup like before:
#IBAction func didTapCategory(_ sender: UIBarButtonItem) {
let popupContent = StructureNavigationListViewController.create()
let cardpopUp = SBCardPopupViewController(contentViewController: popupContent)
cardpopUp.show(onViewController: self)
}
In my StructureNavigationListViewController I set up the table view and the pop up:
public var popupViewController: SBCardPopupViewController?
public var allowsTapToDismissPopupCard: Bool = true
public var allowsSwipeToDismissPopupCard: Bool = true
static func create() -> UIViewController {
let sb = UIStoryboard(name: "Main", bundle: nil)
let vc = sb.instantiateViewController(withIdentifier: "StructureNavigationListViewController") as! StructureNavigationListViewController
return vc
}
#IBOutlet var tableView: UITableView!
var structures = Variable<[Structure]>([])
public var treeSource: StructureTreeSource?
let disposeBag = DisposeBag()
var depthDictionary : [String : Int] = [:]
public override func viewDidLoad() {
structures.asObservable()
.bind(to:tableView.rx.items) {(tableView, row, structure) in
let cell = tableView.dequeueReusableCell(withIdentifier: "StructureNavigationCell", for: IndexPath(row: row, section: 0)) as! StructureNavigationCell
cell.structureLabel.text = structure.name
cell.spacingViewWidthConstraint.constant = 20 * CGFloat(self.depthDictionary[structure.id]!)
return cell
}.disposed(by:disposeBag)
_ = tableView.rx.modelSelected(Structure.self).subscribe(onNext: { structure in
let storyBoard = UIStoryboard(name:"Main", bundle:nil)
let plansViewCtrl = storyBoard.instantiateViewController(withIdentifier: "PlansViewController2") as! PlansViewController2
self.treeSource?.select(structure)
plansViewCtrl.treeSource = self.treeSource
plansViewCtrl.navigationItem.title = structure.name
self.show(plansViewCtrl, sender: self)
if let mainVC = self.parent as? ProjectOverViewTabController2 {
mainVC.addChildView(viewController: plansViewCtrl, in: mainVC.scrollView)
}
})
showList()
}
func showList() {
if treeSource == nil {
treeSource = StructureTreeSource(projectId:GlobalState.selectedProjectId!)
}
//The following piece of code achieves the correct order of structures and their substructures.
//It is extremely bad designed and rather expensive with lots of structures and should
//therefore be refactored!
if let strctrs = getStructures() {
var sortedStructures : [Structure] = []
while(sortedStructures.count != strctrs.count) {
for strct in strctrs {
if let _ = sortedStructures.index(of: strct) {
continue
} else {
depthDictionary[strct.id] = getDepthOfNode(structure: strct, depth: 1)
if let structures = getStructures() {
if let parent = structures.first(where: {$0.id == strct.parentId}) {
if let index = sortedStructures.index(of: parent) {
sortedStructures.insert(strct, at: index+1)
}
} else {
sortedStructures.insert(strct, at: 0)
}
}
}
}
}
structures.value = sortedStructures
tableView.reloadData()
}
}
func getDepthOfNode(structure: Structure, depth: Int) -> Int {
if(structure.parentId == nil || structure.parentId == "") {
return depth
} else {
if let structures = getStructures() {
if let parent = structures.first(where: {$0.id == structure.parentId}) {
return getDepthOfNode(structure: parent, depth: depth + 1)
}
}
}
return -1
}
private func getStructures() -> Results<Structure>? {
do {
if let projectId = GlobalState.selectedProjectId {
return try Structure.db.by(projectId: projectId)
}
} catch { Log.db.error(error: error) }
return nil
}
}
Lot of code here. Sorry..
Is it because I call the create() method after the viewDidLoad() dequeues the cells?
It's hard to tell what is the problem, since you left no information about where didTapCategory is supposed to be called, but maybe it has something to do with your modelSelected subscription being prematurely released?
Edit:
As posted here: https://stackoverflow.com/a/28896452/11851832 if your custom cell is built with Interface Builder then you should register the Nib, not the class:
tableView.registerNib(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: "CustomCellIdentifier")

MLMediaLibrary - Displaying the Photo Library -Swift Code Wrong - macOS

Trying to show photos from the Photo Library. The Apple Sample Code below does not work properly. It doesn't wait for the notifications but tries to launch the collection view which crashes at Number of Items in Section because there is no photo data to show.
Thanks for any help from you Swift guru's out there!
/*
Copyright (C) 2016 Apple Inc. All Rights Reserved.
See LICENSE.txt for this sample’s licensing information
Abstract:
The `ViewController` class is a subclass to NSViewController responsible for managing the app's content.
*/
import Cocoa
import MediaLibrary
class IconViewBox : NSBox {
override func hitTest(_ aPoint: NSPoint) -> NSView? {
// Don't allow any mouse clicks for subviews in this NSBox.
return nil
}
}
// MARK: -
class ViewController: NSViewController, NSCollectionViewDelegate {
// MARK: - Types
// Keys describing the dictionary for each photo loaded.
private struct ItemKeys {
static let imageKey = "icon"
static let nameKey = "name"
}
// MLMediaLibrary property values for KVO.
private struct MLMediaLibraryPropertyKeys {
static let mediaSourcesKey = "mediaSources"
static let rootMediaGroupKey = "rootMediaGroup"
static let mediaObjectsKey = "mediaObjects"
static let contentTypeKey = "contentType"
}
// MARK: - Properties
/**
The KVO contexts for `MLMediaLibrary`.
This provides a stable address to use as the `context` parameter for KVO observation methods.
*/
private var mediaSourcesContext = 1
private var rootMediaGroupContext = 2
private var mediaObjectsContext = 3
private var photoSize = CGSize(width: 168, height: 145)
// Contains an array of dictionaries describing each photo (refer to ItemKeys for key/values).
#IBOutlet weak var arrayController: NSArrayController!
#IBOutlet weak var collectionView: NSCollectionView!
#IBOutlet private weak var noPhotosLabel: NSTextField!
#IBOutlet private weak var activityIndicator: NSProgressIndicator!
// MLMediaLibrary instances for loading the photos.
private var mediaLibrary: MLMediaLibrary!
private var mediaSource: MLMediaSource!
private var rootMediaGroup: MLMediaGroup!
// MARK: - View Controller Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
print ("VDL1...................")
// Start progress indicator in case fetching the photos from the photo library takes time.
self.activityIndicator.isHidden = false
self.activityIndicator.startAnimation(self)
self.collectionView.minItemSize = self.photoSize
self.collectionView.maxItemSize = self.photoSize
self.arrayController.setSelectionIndex(-1) // No selection to start out with.
// Setup the media library to load only photos, don't include other source types.
let options: [String : AnyObject] =
[MLMediaLoadSourceTypesKey: MLMediaSourceType.image.rawValue as AnyObject,
MLMediaLoadIncludeSourcesKey: [MLMediaSourcePhotosIdentifier, MLMediaSourceiPhotoIdentifier] as AnyObject]
// Create our media library instance to get our photo.
mediaLibrary = MLMediaLibrary(options: options)
// We want to be called when media sources come in that's available (via observeValueForKeyPath).
self.mediaLibrary.addObserver(self,
forKeyPath: MLMediaLibraryPropertyKeys.mediaSourcesKey,
options: NSKeyValueObservingOptions.new,
context: &mediaSourcesContext)
if (self.mediaLibrary.mediaSources != nil) {
print ("VDL2...................")
} // Reference returns nil but starts the asynchronous loading.
}
deinit {
// Make sure to remove us as an observer before "mediaLibrary" is released.
self.mediaLibrary.removeObserver(self, forKeyPath: MLMediaLibraryPropertyKeys.mediaSourcesKey, context:&mediaSourcesContext)
}
// MARK: - NSCollectionViewDataSource
func numberOfSectionsInCollectionView(_ collectionView: NSCollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
let photos = self.arrayController.arrangedObjects as! NSArray
return photos.count
}
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAtIndexPath indexPath: IndexPath) -> NSCollectionViewItem {
let item = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "IconItem"), for:indexPath)
let photos = self.arrayController.arrangedObjects as! NSArray
let iconInfo = photos[(indexPath as NSIndexPath).item]
item.representedObject = iconInfo
return item
}
// MARK: - NSCollectionViewDelegate
func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set<IndexPath>) {
if let itemIndexPath = indexPaths.first {
let photos = self.arrayController.arrangedObjects as! NSArray
let itemDict = photos[((itemIndexPath as NSIndexPath).item)] as! NSDictionary
if let itemTitle = itemDict[ItemKeys.nameKey] as? String {
if (itemTitle.characters.count > 0) {
print("selected photo: '\(itemTitle)'")
}
else {
print("selected photo: <no title>")
}
}
}
}
// MARK: - Utilities
/// Helps to make sure the media object is the photo format we want.
private func isValidImage(_ mediaObject: MLMediaObject) -> Bool {
var isValidImage = false
let attrs = mediaObject.attributes
let contentTypeStr = attrs[MLMediaLibraryPropertyKeys.contentTypeKey] as! String
// We only want photos, not movies or older PICT formats (PICT image files are not supported in a sandboxed environment).
if ((contentTypeStr != kUTTypePICT as String) && (contentTypeStr != kUTTypeQuickTimeMovie as String))
{
isValidImage = true
}
return isValidImage
}
/// Obtains the title of the MLMediaObject (either the meta name or the last component of the URL).
func imageTitle(_ fromMediaObject: MLMediaObject) -> String {
guard let title = fromMediaObject.attributes["name"] else {
return fromMediaObject.url!.lastPathComponent
}
return title as! String
}
// MARK: - Photo Loading
/// Observer for all key paths returned from the MLMediaLibrary.
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
print ("Observe1...................")
if (keyPath == MLMediaLibraryPropertyKeys.mediaSourcesKey && context == &mediaSourcesContext && object! is MLMediaLibrary) {
// The media sources have loaded, we can access the its root media.
if let mediaSource = self.mediaLibrary.mediaSources?[MLMediaSourcePhotosIdentifier] {
self.mediaSource = mediaSource
}
else if let mediaSource = self.mediaLibrary.mediaSources?[MLMediaSourceiPhotoIdentifier] {
self.mediaSource = mediaSource
}
else {
print ("Observe2...................")
// Can't find any media sources.
self.noPhotosLabel.isHidden = false
// Stop progress indicator.
self.activityIndicator.isHidden = true
self.activityIndicator.stopAnimation(self)
return // No photos found.
}
// Media Library is loaded now, we can access mediaSource for photos
self.mediaSource.addObserver(self,
forKeyPath: MLMediaLibraryPropertyKeys.rootMediaGroupKey,
options: NSKeyValueObservingOptions.new,
context: &rootMediaGroupContext)
// Obtain the media grouping (reference returns nil but starts asynchronous loading).
if (self.mediaSource.rootMediaGroup != nil) {}
}
else if (keyPath == MLMediaLibraryPropertyKeys.rootMediaGroupKey && context == &rootMediaGroupContext && object! is MLMediaSource) {
print ("Observe3...................")
// The root media group is loaded, we can access the media objects.
// Done observing for media groups.
self.mediaSource.removeObserver(self, forKeyPath: MLMediaLibraryPropertyKeys.rootMediaGroupKey, context:&rootMediaGroupContext)
self.rootMediaGroup = self.mediaSource.rootMediaGroup
self.rootMediaGroup.addObserver(self,
forKeyPath: MLMediaLibraryPropertyKeys.mediaObjectsKey,
options: NSKeyValueObservingOptions.new,
context: &mediaObjectsContext)
// Obtain the all the photos, (reference returns nil but starts asynchronous loading).
if (self.rootMediaGroup.mediaObjects != nil) {}
}
else if (keyPath == MLMediaLibraryPropertyKeys.mediaObjectsKey && context == &mediaObjectsContext && object! is MLMediaGroup) {
print ("Observe4...................")
// The media objects are loaded, we can now finally access each photo.
// Done observing for media objects that group.
self.rootMediaGroup.removeObserver(self, forKeyPath: MLMediaLibraryPropertyKeys.mediaObjectsKey, context:&mediaObjectsContext)
// Stop progress indicator since we know if we have photos (or not).
self.activityIndicator.isHidden = true
self.activityIndicator.stopAnimation(self)
let mediaObjects = self.rootMediaGroup.mediaObjects
if (mediaObjects != nil && mediaObjects!.count > 0) {
print ("Observe5...................")
// Add photos to the array, to be used in our NSCollectionView.
for mediaObject in mediaObjects! {
if (self.isValidImage(mediaObject)) { // Make sure the media object is a photo.
let title = self.imageTitle(mediaObject)
if let image = NSImage.init(contentsOf: mediaObject.thumbnailURL!) {
let iconItem : Dictionary = [ItemKeys.imageKey: image, ItemKeys.nameKey: title] as [String : Any]
self.arrayController.addObject(iconItem)
}
}
}
self.collectionView.reloadData()
}
else {
// No photos available.
self.noPhotosLabel.isHidden = false
}
self.rootMediaGroup = nil // We are done with this.
}
else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
}
NSCollectionViewDataSource delegate is missing
class ViewController: NSViewController, NSCollectionViewDelegate, NSCollectionViewDataSource
Next you need to fix these methods: 'numberOfSectionsInCollectionView' has been renamed to 'numberOfSections(in:)' 'collectionView(:itemForRepresentedObjectAtIndexPath:)' has been renamed to 'collectionView(:itemForRepresentedObjectAt:)'