【问题标题】:MLMediaLibrary - Displaying the Photo Library -Swift Code Wrong - macOSMLMediaLibrary - 显示照片库 - Swift 代码错误 - macOS
【发布时间】:2019-08-27 12:52:14
【问题描述】:

尝试显示照片库中的照片。下面的 Apple 示例代码无法正常工作。它不会等待通知,而是尝试启动在“部分中的项目数”处崩溃的集合视图,因为没有要显示的照片数据。

感谢 Swift 大师的任何帮助!

 /*
 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)
        }
    }

}

【问题讨论】:

  • numberOfItemsInSection 返回0 就可以了。请发布指向 Apple 示例代码的链接。你做了什么改变吗?
  • Willeke,感谢您查看此内容。我所做的唯一更改是尝试了解发生了什么的打印行,它不会超过第一行打印。对于 macOS 上的其他 swift 代码,我们在时间上遇到了类似的问题。这是代码的链接。似乎连 Apple 都跟不上所有的 swift 版本。这是 swift 3,不再支持。再次感谢您的任何帮助developer.apple.com/library/archive/samplecode/…
  • 不应该ViewController采用NSCollectionViewDataSource吗?尝试class ViewController: NSViewController, NSCollectionViewDelegate, NSCollectionViewDataSource 并修复错误。
  • 有趣..我们通常在 storyboard 中设置 dataSource 委托。但是,在情节提要中设置它时,编译器会识别代码中的版本错误。这两种方法在 swift 4 或 5 中都得到了“增强”: 'numberOfSectionsInCollectionView' 已重命名为 'numberOfSections(in:)' 'collectionView(:itemForRepresentedObjectAtIndexPath:)' 已重命名为 'collectionView( :itemForRepresentedObjectAt:)' 进行这些更正可以解决问题。但是,仍然没有照片被退回,所以还有一个问题......谢谢!
  • 这可能是一个隐私问题。尝试将NSPhotoLibraryUsageDescription 添加到 info.plist。

标签: swift xcode macos cocoa icloud-photo-library


【解决方案1】:

NSCollectionViewDataSource 委托丢失

类视图控制器:NSViewController、NSCollectionViewDelegate、NSCollectionViewDataSource

接下来您需要修复这些方法:“numberOfSectionsInCollectionView”已重命名为“numberOfSections(in:)”“collectionView(:itemForRepresentedObjectAtIndexPath:)”已重命名为“collectionView(:itemForRepresentedObjectAt:)”

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-03-20
    • 1970-01-01
    • 2018-06-25
    • 2012-08-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多