Mac application development note

2018-10-03  本文已影响46人  Visitor

Mac开发不同于iOS开发,无需考虑太多性能消耗、动画特效、流畅性问题,但也引入了许多新的知识点,Apple提供给Mac开发的许多权限是iOS没有的。

这两天在开发一款本地音乐播放器,简单的界面,支持数据永久化缓存、双击播放器界面选择文件导入、或从Mac文件目录里选择文件直接拖拽到音乐播放器里播放。
效果如下:


Screen Shot 2018-10-03 at 2.24.38 AM.png

当然也包括Status bar里的自定义控件,效果和QQ音乐相仿,大致如下:


status bar.png

1、文件导出到播放器很简单,只需几行代码即可实现,首先监听Mac鼠标点击事件,当触发点击事件时,判断是否是双击,如果是则处理点击事件,代码如下:

- (void)mouseUp:(NSEvent *)event {
    if(event.clickCount >= 2) {
        NSOpenPanel *openPanel = [NSOpenPanel openPanel];
        openPanel.canChooseFiles = YES;
        openPanel.canChooseDirectories = YES;
        openPanel.canResolveUbiquitousConflicts = YES;
        // 允许同时导入多个文件
        openPanel.allowsMultipleSelection = YES;
        NSInteger result = [openPanel runModal];
        if(result == NSModalResponseOK) {
            [self  resetPlayerWithURL:openPanel.URL];
        }
    }
}

音乐播放使用的是AVFoundation框架,AVPlayer、AVPlayerItem来实现的,因为本地音乐文件有的是有封面等信息的,如下图:

cover.png 使用了AVURLAsset来获取导入音频文件中的歌曲信息 4D0B59FAE1706D184BDE6D386C0BC694.png

代码如下:

- (AVURLAsset *)assetWithURL:(NSURL *)url {
    AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil];
    for(NSString *format in asset.availableMetadataFormats) {
        for(AVMetadataItem *metaDataItem in [asset metadataForFormat:format]) {
            // cover
            if([metaDataItem.commonKey isEqualToString:AVMetadataCommonKeyArtwork]) {
                NSImage *image = [[NSImage alloc] initWithData:(NSData *)metaDataItem.value];
                self.coverImage.image = image ? image : [NSImage imageNamed:@"IMG_2567"];
            }
            
            // name
            if([metaDataItem.commonKey isEqualToString:AVMetadataCommonKeyTitle]) {
                self.songNameLabel.stringValue = (NSString *)metaDataItem.value;
            }
            
            // musician
            if([metaDataItem.commonKey isEqualToString:AVMetadataCommonKeyArtist]) {
//                self.artistLabel.stringValue = (NSString *)metaDataItem.value;
            }            
        }
    }
    return asset;
}

AVFoundation框架里的AVMetadataFormat文件里提供了很多MedadataKey,可根据需求获取

// Metadata common keys
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyTitle                                      NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyCreator                                    NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeySubject                                    NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyDescription                                NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyPublisher                                  NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyContributor                                NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyCreationDate                               NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyLastModifiedDate                           NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyType                                       NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyFormat                                     NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyIdentifier                                 NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeySource                                     NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyLanguage                                   NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyRelation                                   NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyLocation                                   NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyCopyrights                                 NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyAlbumName                                  NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyAuthor                                     NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyArtist                                     NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyArtwork                                    NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyMake                                       NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyModel                                      NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeySoftware                                   NS_AVAILABLE(10_7, 4_0);

2、拖动文件到播放器的流程,首先在播放器NSView界面,初始化的时候注册

- (instancetype)initWithCoder:(NSCoder *)decoder {
    if(self = [super initWithCoder:decoder]) {
        // 注册所有文件格式
        NSString *UTTypeString = (__bridge NSString *)kUTTypeURL;
        [self registerForDraggedTypes:[NSArray arrayWithObject:UTTypeString]];
    }
    return self;
}

其次实现拖动文件的代理

- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
    NSPasteboard *pasteboard = [sender draggingPasteboard];
    NSString *audioVisualContent = (__bridge NSString *)kUTTypeAudiovisualContent;
    NSDictionary *filteringOptions =[NSDictionary dictionaryWithObject:audioVisualContent forKey:NSPasteboardURLReadingFileURLsOnlyKey];
    if([pasteboard canReadObjectForClasses:@[[NSURL class]] options:filteringOptions]) {
        return NSDragOperationCopy;
    }
    return NSDragOperationNone;
}

- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
    NSArray *aboardArray = [[sender draggingPasteboard] propertyListForType:NSFilenamesPboardType];
    NSURL *url = [NSURL fileURLWithPath:aboardArray.firstObject];
    if(self.draggingFile) {
        self.draggingFile(url);
    }
    return YES;
}

这里的draggingFile是一个block,当系统检测到有文件拖动到播放器界面时,获取音频文件的本地路径,回掉给播放器播放

typedef void(^PasteboardResponsedBlock)(NSURL *dragingPath);
@property (nonatomic, strong) PasteboardResponsedBlock draggingFile;

3、添加播放器到Mac中,Xcode - Target - Info, 选择Document Types,增加一个types


Screen Shot 2018-10-03 at 3.09.17 AM.png

设置图标,Extensions是支持播放的音频文件格式,这里仅支持mp3格式的音频文件,设置完成后,当我们右键点击本地mp3文件弹出菜单选择播放器时,就可以看到自定义的播放器了

Screen Shot 2018-10-03 at 3.06.36 AM.png

接下来选择AppDelegat文件,实现如下方法,用于监听双击打开音频文件的Mac本地路径:

- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
    // 双击选择音频文件的本地路径, 回调给AVPlayer播放
    /Users/xxx/Desktop/timegoesby.mp3
    return YES;
}

当然,在我们运行App后,点击关闭无法再次打开时,需要在AppDelegate里面,在与AppDelegate绑定的NSWindowController里,添加如下代码,这样当关闭App,在Dock里点击App图标依然可以打开

- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag {
    [self.windowController.window makeKeyAndOrderFront:nil];
    return YES;
}

4、当然如果设置了App图标,在Dock里依然可以显示该App

Dock.png
上一篇下一篇

猜你喜欢

热点阅读