我有一个NSTextView,可以拖放图片和mp4的。mov和pdf只能拖入,但不能拖出,因为它们不能被选中,只能回放。我喜欢和Apple的Notes App一样的功能。可以选择这些图像和视频(mov、mp4)并将其拖入和拖出。
我花了很多时间在互联网上寻找合适的解决方案,但找不到。
As I select a image, only the glyph is highlighted on the left side. Not the whole frame.
这是Apple的Notes App的截图,可以选择图片和电影,并将其拖入拖出。
Selected image and video in Apple's Notes App
下面是我的自定义textViewController类
//
// RKTextViewController.h
// RKon_OSX
//
// Created by Robert Kyriakis on 23.10.20.
// Copyright ©2020 RKon.eu. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import "RKTextView.h"
/*
By double clicking a text attachment from the textView, the file is openend in Preview App.
Making this work whitout errors the preview file must be copied to a location on the file system and exists there till the app terminates.
Here it's copied to the app documents path within the directory /Documents/Previews/
You must delete the 'Previews' directory either by lauching or terminating the app to clean it!
*/
@class RKTextViewController;
@protocol RKTextViewDelegate <NSObject>
@required
- (NSFont*)fontForTextView:(NSTextView*)textView;
- (NSColor*)textColorForTextView:(NSTextView*)textView;
- (NSColor*)placeholderTextColorForTextView:(NSTextView*)textView;
- (NSString*)placeholderStringForTextView:(NSTextView*)textView;
@optional
- (void)textDidBeginEditing:(NSTextView*)textView;
- (void)textDidEndEditing:(NSTextView*)textView;
@end
@interface RKTextViewController : NSViewController
@property (nonatomic, strong) IBOutlet RKTextView *textView;
@property (nonatomic, strong) IBOutlet NSProgressIndicator *progressIndicator;
@property BOOL contentHasChanged;
@property id <RKTextViewDelegate> rkTextViewDelegate;
- (void)updateTextViewWithTextStorage:(NSTextStorage*)textStorage;
@end//
// RKTextViewController.m
// RKon_OSX
//
// Created by Robert Kyriakis on 23.10.20.
// Copyright ©2020 RKon.eu. All rights reserved.
//
#import <RKon_OSX/RKon_OSX.h>
#import "RKTextViewController.h"
@interface RKTextViewController () <NSTextViewDelegate>
@property (nonatomic, strong) NSFont *textViewFont;
@property (nonatomic, strong) NSColor *textColor;
@property (nonatomic, strong) NSColor *placeholderTextColor;
@property (nonatomic, strong) NSString *placeholderString;
@end
@implementation RKTextViewController
- (void)awakeFromNib
{
[self initProperties];
}
#pragma mark - Extensions
- (void)initProperties
{
dispatch_async(dispatch_get_main_queue(), ^{
self.textView.delegate = self;
// this property must be set to determine, when dragging into the textView
// makes changes and the boolean contentHasChanged here can be set
self.textView.rkTextViewController = self;
if (self.rkTextViewDelegate && [self.rkTextViewDelegate respondsToSelector:@selector(fontForTextView:)]) {
self.textViewFont = [self.rkTextViewDelegate fontForTextView:self.textView];
self.textView.font = self->_textViewFont;
}
if (self.rkTextViewDelegate && [self.rkTextViewDelegate respondsToSelector:@selector(textColorForTextView:)]) {
self.textColor = [self.rkTextViewDelegate textColorForTextView:self.textView];
}
if (self.rkTextViewDelegate && [self.rkTextViewDelegate respondsToSelector:@selector(placeholderTextColorForTextView:)]) {
self.placeholderTextColor = [self.rkTextViewDelegate placeholderTextColorForTextView:self.textView];
self.textView.textColor = self->_placeholderTextColor;
}
if (self.rkTextViewDelegate && [self.rkTextViewDelegate respondsToSelector:@selector(placeholderStringForTextView:)]) {
self.placeholderString = [self.rkTextViewDelegate placeholderStringForTextView:self.textView];
self.textView.string = self->_placeholderString;
}
self.textView.allowsUndo = YES;
self.textView.selectable = YES;
self.textView.richText = YES;
self.textView.importsGraphics = YES;
self.textView.allowsImageEditing = YES;
self.textView.automaticLinkDetectionEnabled = YES;
self.textView.automaticDataDetectionEnabled = YES;
self.textView.smartInsertDeleteEnabled = YES;
// automitically scale larger images or other content to fit in the textView
self.textView.layoutManager.defaultAttachmentScaling = NSImageScaleProportionallyDown;
self.textView.layoutManager.allowsNonContiguousLayout = YES;
self.contentHasChanged = NO;
[RKLogger logDebugInfoFromObject:self withSelector:_cmd log:@"contentHasChanged = NO"];
// register video pboard types
// videos are not allowed jet. They can be dropped, but not dragged out of the textView
// NSString *movPboardType = NSCreateFileContentsPboardType(@"mov");
// NSString *mp4PboardType = NSCreateFileContentsPboardType(@"mp4");
// [self.textView registerForDraggedTypes:@[NSFilenamesPboardType,movPboardType,mp4PboardType]];
});
}
- (void)updateTextViewWithTextStorage:(NSTextStorage*)textStorage
{
[_progressIndicator startAnimation:self];
dispatch_async(dispatch_get_main_queue(), ^{
NSTextStorage *newTextStorage;
NSData *textStorageData;
NSAttributedString *attributedString;
if (textStorage) {
// check the data of the textStorage,
// because the storage can have only glyphs with attachments
textStorageData = [NSKeyedArchiver archivedDataWithRootObject:textStorage];
attributedString = [NSKeyedUnarchiver unarchiveObjectWithData:textStorageData];
// first manage the textStorage, with default font and text color
if (attributedString && (attributedString.length > 0)) {
newTextStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString];
} else {
newTextStorage = [[NSTextStorage alloc] initWithString:self->_placeholderString];
}
} else {
// textStorage is Nil
// return only placeholder string
newTextStorage = [[NSTextStorage alloc] initWithString:self->_placeholderString];
}
[self.textView.layoutManager replaceTextStorage:newTextStorage];
// Important textColor and font must be set after
// the textStorage is replaced in the noteTextView to have an effect
self.textView.font = self->_textViewFont;
if (attributedString && (attributedString.length > 0)) {
self.textView.textColor = self->_textColor;
} else {
self.textView.textColor = self->_placeholderTextColor;
}
// TextView must be redrawn in the case of large attributes
// to take effect with delay on main runloop
[self performSelector:@selector(refreshTextView) withObject:Nil afterDelay:0.02];
});
}
- (void)refreshTextView
{
dispatch_async(dispatch_get_main_queue(), ^{
NSRect rect = self.textView.bounds;
[self.textView setNeedsDisplayInRect:rect
avoidAdditionalLayout:YES];
[self.progressIndicator stopAnimation:self];
});
}
#pragma mark - NSTextViewDelegate methods
- (BOOL)textShouldBeginEditing:(NSText*)textObject
{
dispatch_async(dispatch_get_main_queue(), ^{
// while editing the note text set always the text color
// some times it chanes after drop of images
textObject.textColor = self->_textColor;
textObject.font = self->_textViewFont;
});
return YES;
}
- (void)textDidBeginEditing:(NSNotification *)notification
{
dispatch_async(dispatch_get_main_queue(), ^{
NSTextView *textView = (NSTextView*)notification.object;
// while editing the note text set always the text color
// some times it chanes after drop of images
textView.textColor = self->_textColor;
textView.font = self->_textViewFont;
if (self.rkTextViewDelegate && [self.rkTextViewDelegate respondsToSelector:@selector(textDidBeginEditing:)]) {
[self.rkTextViewDelegate textDidBeginEditing:textView];
}
// removing placeholderstring when typing the first
// character doesn't work, when an object is dropped into the textView first
/*
// remove placeholder string, when it's at the begining
NSString *string = textView.textStorage.string;
NSRange placeholderStringRange = [string rangeOfString:self->_placeholderString options:NSCaseInsensitiveSearch];
if ((placeholderStringRange.location == 0) && (placeholderStringRange.length == self->_placeholderString.length)) {
textView.string = [textView.string stringByReplacingOccurrencesOfString:self->_placeholderString withString:@""];
}
*/
});
}
- (void)textDidEndEditing:(NSNotification *)notification
{
NSTextView *textView = (NSTextView*)notification.object;
if (self.rkTextViewDelegate && [self.rkTextViewDelegate respondsToSelector:@selector(textDidBeginEditing:)]) {
[self.rkTextViewDelegate textDidEndEditing:textView];
}
_contentHasChanged = YES;
[RKLogger logDebugInfoFromObject:self withSelector:_cmd log:@"contentHasChanged = YES"];
}
#pragma mark - drag & drop
- (void)textView:(NSTextView*)view
draggedCell:(id<NSTextAttachmentCell>)cell
inRect:(NSRect)rect
event:(NSEvent*)event
atIndex:(NSUInteger)charIndex;
{
[RKLogger logDebugInfoFromObject:Nil withSelector:_cmd log:Nil];
NSTextAttachment *textAttachment = cell.attachment;
// NSString *fileType = textAttachment.fileType;
NSFileWrapper *fileWrapper = textAttachment.fileWrapper;
NSURL *documentsURL = [RKFileManager userApplicationDocumentsURL];
NSURL *fileURL = [documentsURL URLByAppendingPathComponent:fileWrapper.preferredFilename];
// because the method after need a file at path
// first write the fileWrapper to an URL
NSError *error;
[fileWrapper writeToURL:fileURL
options:NSFileWrapperWritingAtomic
originalContentsURL:Nil
error:&error];
if (!error) {
[view dragFile:fileURL.path
fromRect:rect
slideBack:YES
event:event];
// now the file at app docuemnts path is no longer neccesary
// and can be deleted
[[NSFileManager defaultManager] removeItemAtPath:fileURL.path
error:&error];
}
if (error) {
[RKError showRKAlertWithError:error completion:^{
}];
}
// This is not working, only the attributed string with pblic.rtf pboard is dragged
// NSTextAttachment *textAttachment = cell.attachment;
// NSAttributedString *attributedString = [NSAttributedString attributedStringWithAttachment:textAttachment];
// NSDraggingItem *draggingItem = [[NSDraggingItem alloc] initWithPasteboardWriter:attributedString];
// draggingItem.draggingFrame = rect;
// [self.textView beginDraggingSessionWithItems:@[draggingItem]
// event:event
// source:self.textView];
}
@end下面是我的自定义textView类
//
// RKTextView.h
// RKon_OSX
//
// Created by Robert Kyriakis on 26.10.20.
// Copyright ©2020 RKon.eu. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import "RKTextViewController.h"
@class RKTextViewController;
/*
By double clicking a text attachment from the textView, the file is openend in Preview App.
Making this work whitout errors the preview file must be copied to a location on the file system and exists there till the app terminates.
Here it's copied to the app documents path within the directory /Documents/Previews/
You must delete the 'Previews' directory either by lauching or terminating the app to clean it!
*/
@interface RKTextView : NSTextView
@property (nonatomic, strong) RKTextViewController *rkTextViewController;
@end//
// RKTextView.m
// RKon_OSX
//
// Created by Robert Kyriakis on 26.10.20.
// Copyright ©2020 RKon.eu. All rights reserved.
//
#import <RKon_OSX/RKon_OSX.h>
#import "RKTextView.h"
@interface RKTextView ()
//@property NSPoint dropPoint;
@end
@implementation RKTextView
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// Drawing code here.
}
- (void)mouseDown:(NSEvent*)event
{
if (event.clickCount == 2) {
// doubleClikk detected
// get fileWrapper of the attributedString at mouse point
NSPoint doubleClickPoint = [self convertPoint:[event locationInWindow]
fromView:nil];
NSUInteger attribIndex = [self characterIndexForInsertionAtPoint:doubleClickPoint];
// get the attributes at index
NSDictionary <NSAttributedStringKey, id> *attributesDict = [self.textStorage attributesAtIndex:attribIndex effectiveRange:Nil];
// set the attachment, if exists
NSTextAttachment *attachment = [attributesDict valueForKey:NSAttachmentAttributeName];
// check, if an attachment exists
if (attachment) {
// there is an attachment with a fileWrapper
NSFileWrapper *fileWrapper = attachment.fileWrapper;
// bevor oppening the file in an app it has to be stored at a path.
// It can't be deleted from this path, while an application like Preview want's to save it later!!
// You must delete all files from that folder when lounging the app.
NSURL *documentsURL = [RKFileManager userApplicationDocumentsURL];
NSURL *docPreviewsURL = [documentsURL URLByAppendingPathComponent:@"Previews"];
// check, if previews path exists
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
BOOL isDirectory;
if (![fileManager fileExistsAtPath:docPreviewsURL.path isDirectory:&isDirectory]) {
// not exists -> create it
[fileManager createDirectoryAtPath:docPreviewsURL.path
withIntermediateDirectories:NO
attributes:Nil
error:&error];
if (error) {
[RKError showRKAlertWithError:error completion:^{
}];
return;
}
}
NSURL *fileURL = [docPreviewsURL URLByAppendingPathComponent:fileWrapper.preferredFilename];
// because the method after need a file at path
// first write the fileWrapper to an URL
[fileWrapper writeToURL:fileURL
options:NSFileWrapperWritingAtomic
originalContentsURL:Nil
error:&error];
if (!error) {
// open attachment with preview app
NSString *previewAppPath = [[NSWorkspace sharedWorkspace] fullPathForApplication:@"Preview"];
[[NSWorkspace sharedWorkspace] openFile:fileURL.path withApplication:previewAppPath];
}
if (error) {
[RKError showRKAlertWithError:error completion:^{
}];
}
} else {
// no attachement exists, so it's only a text without attachments
// call only super
[super mouseDown:event];
}
} else {
// by ontime mouse click call only super
[super mouseDown:event];
}
NSPoint dragPosition = [self convertPoint:event.locationInWindow
fromView:Nil];
[RKLogger logDebugInfoFromObject:Nil withSelector:_cmd log:[NSString stringWithFormat:@"Dragposition (x/y) (%.0f/%.0f)",dragPosition.x,dragPosition.y]];
}
#pragma mark - Drag & Drop methods
- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender
{
[RKLogger logDebugInfoFromObject:Nil withSelector:_cmd log:Nil];
// return [super performDragOperation:sender];
BOOL performDragginOperation = NO;
NSPasteboard *pboard = [sender draggingPasteboard];
NSArray *pboardTypes = [pboard types];
NSDragOperation dragOperation = NSDragOperationNone;
NSTextAttachment *attachment;
NSAttributedString *attributedString;
// dragging files into the TextView
if ([pboardTypes containsObject:NSFilenamesPboardType]) {
NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];
for (NSString *filePath in files) {
NSString *fileName = filePath.lastPathComponent;
// turn all filename signs to lowercase to compare suffixes
NSString *lowercaseFileName = [fileName lowercaseString];;
if (
[lowercaseFileName hasSuffix:@"jpg"] || [lowercaseFileName hasSuffix:@"jpeg"] ||
[lowercaseFileName hasSuffix:@"png"] ||
[lowercaseFileName hasSuffix:@"gif"] ||
[lowercaseFileName hasSuffix:@"tif"] || [lowercaseFileName hasSuffix:@"tiff"] ||
[lowercaseFileName hasSuffix:@"psd"] ||
[lowercaseFileName hasSuffix:@"pdf"] ||
[lowercaseFileName hasSuffix:@"icns"]
) {
dragOperation = NSDragOperationEvery;
}
if (dragOperation == NSDragOperationEvery) {
// dragged file is valid for dragging
NSError *error;
NSURL *url = [NSURL fileURLWithPath:filePath];
NSFileWrapper *fileWrapper = [[NSFileWrapper alloc] initWithURL:url
options:NSFileWrapperReadingImmediate
error:&error];
if (!error) {
fileWrapper.preferredFilename = filePath.lastPathComponent;
// insert the dragged file at cursor position
attachment = [[NSTextAttachment alloc] initWithFileWrapper:fileWrapper];
attributedString = [NSAttributedString attributedStringWithAttachment:attachment];
NSPoint dropPoint = [self convertPoint:[sender draggingLocation] fromView:Nil];
NSUInteger caretLocation = [self characterIndexForInsertionAtPoint:dropPoint];
// insert the new attachment at dragging cursor position
[self.textStorage insertAttributedString:attributedString atIndex:caretLocation];
// check, if the attributedString already exists and remove it
[self.textStorage enumerateAttribute:NSAttachmentAttributeName
inRange:NSMakeRange(0, self.textStorage.length)
options:0
usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
NSTextAttachment *attachement = (NSTextAttachment*)value;
if ([attachement.fileWrapper.preferredFilename isEqualToString:filePath.lastPathComponent]) {
// same attachment found
// check, if it's the new inserted
if (range.location != caretLocation) {
// is not, so delete it
// [self.textStorage replaceCharactersInRange:range withString:@""];
[self.textStorage deleteCharactersInRange:range];
*stop = YES;
}
}
}];
_rkTextViewController.contentHasChanged = YES;
} else {
[RKError showRKAlertWithError:error completion:Nil];
dragOperation = NSDragOperationNone;
}
}
}
}
if (dragOperation == NSDragOperationEvery) {
performDragginOperation = YES;
} else {
performDragginOperation = NO;
}
return performDragginOperation;
}发布于 2020-11-24 20:15:04
如果你想要能够将媒体从拖拽到你的应用中,那么你需要将你的窗口设置为拖动源和拖拽目的地。
这里有一个关于这个东西的很好的入门教程:https://www.raywenderlich.com/1016-drag-and-drop-tutorial-for-macos
如果你已经看过这篇文章,很抱歉。
https://stackoverflow.com/questions/64946667
复制相似问题