首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >NSTextView高亮显示选定的文本附件(图像和电影),就像在苹果的笔记中一样

NSTextView高亮显示选定的文本附件(图像和电影),就像在苹果的笔记中一样
EN

Stack Overflow用户
提问于 2020-11-22 02:32:49
回答 1查看 111关注 0票数 0

我有一个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类

代码语言:javascript
复制
//
//  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
代码语言:javascript
复制
//
//  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类

代码语言:javascript
复制
//
//  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
代码语言:javascript
复制
//
//  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;

}
EN

回答 1

Stack Overflow用户

发布于 2020-11-24 20:15:04

如果你想要能够将媒体拖拽到你的应用中,那么你需要将你的窗口设置为拖动源和拖拽目的地。

这里有一个关于这个东西的很好的入门教程:https://www.raywenderlich.com/1016-drag-and-drop-tutorial-for-macos

如果你已经看过这篇文章,很抱歉。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/64946667

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档