I've subclasses an NSTextView so that I can drop a file and copy the string contents of the file into the view (as opposed to the standard implementation which drops the filepath into the view). The text seems to be dropping correctly, but then is not visible after the drop. I can see that the cursor has moved and can even copy the dropped text out of the view and paste into, for example, TextEdit. I tried adding [self setNeedsDisplay:YES] at the end of my -performDragOperation: method, but the behavior did not change.
Here's the code I've written so far. I imagine this is not the best way to implement this. I'm new to drag and drop implementation in cocoa.
-(NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
NSPasteboard *pb = [sender draggingPasteboard];
NSDragOperation dragOperation = [sender draggingSourceOperationMask];
if ([[pb types] containsObject:NSFilenamesPboardType]) {
if (dragOperation & NSDragOperationCopy) {
return NSDragOperationCopy;
}
}
if ([[pb types] containsObject:NSPasteboardTypeString]) {
if (dragOperation & NSDragOperationCopy) {
return NSDragOperationCopy;
}
}
return NSDragOperationNone;
}
-(BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
NSPasteboard *pb = [sender draggingPasteboard];
if ( [[pb types] containsObject:NSFilenamesPboardType] ) {
NSArray *filenames = [pb propertyListForType:NSFilenamesPboardType];
for (NSString *filename in filenames) {
NSStringEncoding encoding;
NSError * error;
NSString * fileContents = [NSString stringWithContentsOfFile:filename usedEncoding:&encoding error:&error];
if (error) {
// handle error
}
else {
[self setString:fileContents];
}
}
}
else if ( [[pb types] containsObject:NSPasteboardTypeString] ) {
NSString *draggedString = [pb stringForType:NSPasteboardTypeString];
[self setString:draggedString];
}
return YES;
}
I had a stub for -drawRect: that had no implementation.
After removing the stub, everything works exactly as intended.
Related
So, I have an UITableView which holds entries for an app I am making. The entriesViewController is its own class, with a .xib file. I have a button that adds a new item.
It does this with the following code:
-(IBAction)newItem:(id)sender {
LEItem *newItem = [[LEItemStore sharedStore] createItem];
NSLog(#"New Item = %#", newItem);
[TableView reloadData];
}
Now this works, and adds the item, however it puts it at the bottom of the list. Since this app logs things for days, I do not want the items in this order. The newest items should be placed at the top of the list. How do I do this? I didn't see any easy way to add items to the table view at the top, but I might be missing something pretty basic.
This doesn't seem like it should be hard, I am probably just overlooking something.
Ideas are welcome.
Edit:
Here is LEItem Store:
//
// LEItemStore.m
//
// Created by Josiah Bruner on 10/16/12.
// Copyright (c) 2012 Infinite Software Technologies. All rights reserved.
//
#import "LEItemStore.h"
#import "LEItem.h"
#implementation LEItemStore
+ (LEItemStore *)sharedStore
{
static LEItemStore *sharedStore = nil;
if (!sharedStore)
sharedStore = [[super allocWithZone:nil] init];
return sharedStore;
}
+ (id)allocWithZone:(NSZone *)zone
{
return [self sharedStore];
}
-(id)init
{
self = [super init];
if (self) {
NSString *path = [self itemArchivePath];
allItems = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
if (!allItems)
{
allItems = [[NSMutableArray alloc] init];
}
}
return self;
}
- (NSArray * )allItems
{
return allItems;
}
-(LEItem *)createItem
{
LEItem *p = [LEItem addNewItem];
[allItems addObject:p];
return p;
}
- (void)removeItem:(LEItem *)p
{
[allItems removeObjectIdenticalTo:p];
}
-(void)moveItemAtIndex:(int)from toIndex:(int)to
{
if (from == to) {
return;
}
LEItem *p = [allItems objectAtIndex:from];
[allItems removeObjectAtIndex:from];
[allItems insertObject:p atIndex:to];
}
- (NSString *)itemArchivePath {
NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [documentDirectories objectAtIndex:0];
return [documentDirectory stringByAppendingPathComponent:#"item.archive"];
}
-(BOOL)saveChanges {
NSString *path = [self itemArchivePath];
return [NSKeyedArchiver archiveRootObject:allItems toFile:path];
}
#end
It looks like the simplest solution would be to modify -[LEItemStore createItem] to this:
-(LEItem *)createItem {
LEItem *p = [LEItem addNewItem];
[allItems insertObject:p atIndex:0];
return p;
}
You can do it even without rearrange the array internally.If you implement the data source and you define this method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
Assuming that in your array the oldest objects are at the lowest indexes,supposing that your table view has M rows, return a cell with the format of the object at index M-rowIndex-1.
Unless I'm missing something, after you create the new item, instead of using
[allItems addObject:p];
you just need:
[allItems insertObject:p atIndex:0];
Do you have any type of createdDate or other sortable property on the item? Simply sort your retained list of items (or NSFetchedResultsController) or whatever you are binding to by that property.
You can override the comparison mechanism in your LEItem class, and have it compare dates easily:
-(NSComparisonResult)compare:(LEItem*)otherItem {
return [self.dateCreated compare:otherItem.dateCreated];
}
Then, it's just a matter of using sortArrayUsingSelector: with the selector compare:.
I have a Document based Application, with an TextView.
I want to add an open feature that writes it in the TextView.
I have the code, but it woundn't work.
The TextView is empty.
Heres my code:
#import "Document.h"
#implementation Document
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
NSFont *courier = [NSFont fontWithName: #"Courier" size:12];
[_textView setString: #"Blabla"];
[_textView setFont:courier];
NSLog(#"Tesg");
[_textView setString:#"TEST"];
}
- (id)init
{
NSLog(#"Tesg");
self = [super init];
if (self) {
// Add your subclass-specific initialization here.
}
return self;
}
- (NSString *)windowNibName
{
// Override returning the nib file name of the document
// If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this method and override -makeWindowControllers instead.
return #"Document";
}
- (void)windowControllerDidLoadNib:(NSWindowController *)aController
{
[super windowControllerDidLoadNib:aController];
// Add any code here that needs to be executed once the windowController has loaded the document's window.
}
+ (BOOL)autosavesInPlace
{
return YES;
}
/*- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError
{
// Insert code here to write your document to data of the specified type. If outError != NULL, ensure that you create and set an appropriate error when returning nil.
// You can also choose to override -fileWrapperOfType:error:, -writeToURL:ofType:error:, or -writeToURL:ofType:forSaveOperation:originalContentsURL:error: instead.
NSException *exception = [NSException exceptionWithName:#"UnimplementedMethod" reason:[NSString stringWithFormat:#"%# is unimplemented", NSStringFromSelector(_cmd)] userInfo:nil];
#throw exception;
return nil;
}
*/
- (NSData *)dataOfType:(NSString *)pTypeName error:(NSError **)pOutError {
NSDictionary * zDict;
if ([pTypeName compare:#"public.plain-text"] == NSOrderedSame ) {
zDict = [NSDictionary dictionaryWithObjectsAndKeys:
NSPlainTextDocumentType,
NSDocumentTypeDocumentAttribute,nil];
} else {
NSLog(#"ERROR: dataOfType pTypeName=%#",pTypeName);
*pOutError = [NSError errorWithDomain:NSOSStatusErrorDomain
code:unimpErr
userInfo:NULL];
return NULL;
} // end if
NSString * zString = [[_textView textStorage] string];
NSData * zData = [zString dataUsingEncoding:NSASCIIStringEncoding];
return zData;
} // end dataOfType
/*
- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError
{
// Insert code here to read your document from the given data of the specified type. If outError != NULL, ensure that you create and set an appropriate error when returning NO.
// You can also choose to override -readFromFileWrapper:ofType:error: or -readFromURL:ofType:error: instead.
// If you override either of these, you should also override -isEntireFileLoaded to return NO if the contents are lazily loaded.
NSLog(data);
NSException *exception = [NSException exceptionWithName:#"UnimplementedMethod" reason:[NSString stringWithFormat:#"%# is unimplemented", NSStringFromSelector(_cmd)] userInfo:nil];
#throw exception;
return YES;
}
*/
- (BOOL)readFromData:(NSData *)pData
ofType:(NSString *)pTypeName
error:(NSError **)pOutError {
if ([pTypeName compare:#"public.plain-text"] != NSOrderedSame) {
NSLog(#"** ERROR ** readFromData pTypeName=%#",pTypeName);
*pOutError = [NSError errorWithDomain:NSOSStatusErrorDomain
code:unimpErr
userInfo:NULL];
return NO;
} // end if
NSDictionary *zDict = [NSDictionary dictionaryWithObjectsAndKeys:
NSPlainTextDocumentType,
NSDocumentTypeDocumentAttribute,
nil];
NSDictionary *zDictDocAttributes;
NSError *zError = nil;
zNSAttributedStringObj =
[[NSAttributedString alloc]initWithData:pData
options:zDict
documentAttributes:&zDictDocAttributes
error:&zError];
if ( zError != NULL ) {
NSLog(#"Error readFromData: %#",[zError localizedDescription]);
return NO;
} // end if
NSString *content = [zNSAttributedStringObj string];
NSLog(#"%#", content);
NSLog(#"%c", [_textView isEditable]);
[_textView setString:content];
return YES;
} // end readFromData
#end
Thanks!
Please do not flag it as "Not a real Question" or something else.
The problem is the readXXX methods are called before the window is created. This means that _textView is nil. You need to use -(void)windowControllerDidLoadNib:(NSWindowController *)windowController to populate _textView with the information you load from the file.
You can avoid being caught out by this kind of problem in future by placing NSAssert calls in your code to confirm the preconditions required for your methods to operate correctly:
NSAssert(_textView != nil, #"_textView not initialized");
I've created an app, containing an ImageView subclass which accepts drag'n'dropping files/folders directly from Finder.
The thing is I'm now trying to make it accept photos, either from iPhoto or Aperture, as well.
Which PboardTypes should I register for?
All I'm currently doing is :
[self registerForDraggedTypes:
[NSArray arrayWithObjects:NSFilenamesPboardType, nil]];
Any ideas?
Using Pasteboard Peeker (from Apple) shows me that Aperture gives you file names/URLs as well as "aperture image data" (whatever that is). iPhoto appears only to give "ImageDataListPboardType", which is a PLIST. I'm guessing you could NSLog() that out to see its structure and pull the image information from it. It may possibly include the filename/URL info as well as the actual image as data.
You are correct to register for NSFilenamesPboardType. To complete the task:
1: Make sure you accept the copy operation in draggingEntered. The generic operation is insufficient.
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
NSDragOperation sourceDragMask = [sender draggingSourceOperationMask];
NSPasteboard *pasteboard = [sender draggingPasteboard];
if ( [[pasteboard types] containsObject:NSFilenamesPboardType] ) {
if (sourceDragMask & NSDragOperationCopy) {
return NSDragOperationCopy;
}
}
return NSDragOperationNone;
}
2: There will be one filename per photo. Do something with them.
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
NSPasteboard *pasteboard;
NSDragOperation sourceDragMask;
sourceDragMask = [sender draggingSourceOperationMask];
pasteboard = [sender draggingPasteboard];
if ([[pasteboard types] containsObject:NSFilenamesPboardType])
{
NSData* data = [pasteboard dataForType:NSFilenamesPboardType];
if(data)
{
NSString *errorDescription;
NSArray *filenames = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:kCFPropertyListImmutable format:nil errorDescription:&errorDescription];
for (NSString* filename in filenames)
{
NSImage* image = [[NSImage alloc]initWithContentsOfFile:filename];
//Do something with the image
}
}
}
return YES;
}
I'm trying to understand how to use NSTreeController. When I added a method (segment of the SourceView example's class and method are provided below) to iterate the contents of the directory, which is transmitted through NSTreeController to the NSOutlineView. But, NSOutlineView displays only the first objects (root objects) (you can be seen it on the schemes below).
Class NSTreeController Methods:
- (void)performAddChild:(TreeAdditionObj *)treeAddition {
if ([[treeController selectedObjects] count] > 0) {
// we have a selection
if ([[[treeController selectedObjects] objectAtIndex:0] isLeaf]) {
// trying to add a child to a selected leaf node, so select its parent for add
[self selectParentFromSelection];
}
}
// find the selection to insert our node
NSIndexPath *indexPath;
if ([[treeController selectedObjects] count] > 0) {
// we have a selection, insert at the end of the selection
indexPath = [treeController selectionIndexPath];
indexPath = [indexPath indexPathByAddingIndex:[[[[treeController selectedObjects] objectAtIndex:0] children] count]];
} else {
// no selection, just add the child to the end of the tree
indexPath = [NSIndexPath indexPathWithIndex:[contents count]];
}
// create a leaf node
BaseNode *node = [[BaseNode alloc] initLeaf];
node.urlString = [treeAddition nodeURL];
if ([treeAddition nodeURL]) {
if ([[treeAddition nodeURL] length] > 0) {
// the child to insert has a valid URL, use its display name as the node title
if ([treeAddition nodeName])
node.nodeTitle = [treeAddition nodeName];
else
node.nodeTitle = [[NSFileManager defaultManager] displayNameAtPath:[node urlString]];
}
}
// the user is adding a child node, tell the controller directly
[treeController insertObject:node atArrangedObjectIndexPath:indexPath];
// adding a child automatically becomes selected by NSOutlineView, so keep its parent selected
if ([treeAddition selectItsParent])
[self selectParentFromSelection];
}
- (void)addChild:(NSString *)url withName:(NSString *)nameStr selectParent:(BOOL)select {
TreeAdditionObj *treeObjInfo = [[TreeAdditionObj alloc] initWithURL:url
withName:nameStr
selectItsParent:select];
if (buildingOutlineView) {
// add the child node to the tree controller, but on the main thread to avoid lock ups
[self performSelectorOnMainThread:#selector(performAddChild:)
withObject:treeObjInfo
waitUntilDone:YES];
} else {
[self performAddChild:treeObjInfo];
}
}
Method for displaying enumerated objects of the directory:
- (void)addFinderSection {
[self addFolder:#"FINDER FILES"];
NSError *error = nil;
NSEnumerator *urls = [[[NSFileManager defaultManager] contentsOfDirectoryAtURL:self.url includingPropertiesForKeys:[NSArray arrayWithObjects: nil] options:(NSDirectoryEnumerationSkipsHiddenFiles) error:&error] objectEnumerator];
for (NSURL *url in urls) {
BOOL isDirectory;
if ([[NSFileManager defaultManager] fileExistsAtPath:[url path] isDirectory:&isDirectory]) {
if (isDirectory) {
if (![[NSWorkspace sharedWorkspace] isFilePackageAtPath:[url path]]) {
NSLog(#"IS DIRECTORY %#", url);
[self addChild:[url path] withName:NO selectParent:YES];
}
} else {
NSLog(#"IS FIlE %#", url);
[self addChild:[url path] withName:NO selectParent:YES];
}
}
}
[self selectParentFromSelection];
}
My mistake is probably that is NSTreeController does not differentiate between the simple objects (files) and the directories (folders).
However, when I fill NSOutlineView without NSTreeController, all content is displayed are correctly:
NOTE: If use method addFolder: (in SourceView example it used for create parent groups for other objects) then, every next object displayed as a subgroup of previous.
Can you help me to correctly display the folder contents in these methods?
I've spent two days googling and reading the Bluetooth programming guide while trying to piece together a small Mac app that will retrieve images from a drop folder and send any new files to a predetermined device over Bluetooth. There doesn't seem to be many good examples available.
I'm at the point where I'm able to spawn the Bluetooth Service Browser and select the device and its OBEX service, establishing a service and creating a connection, but then nothing more happens. Could anyone please point me in the direction of/show me a simple example that would work?
AppDelegate source code enclosed. Thanks for reading!
#import "AppDelegate.h"
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
IOBluetoothServiceBrowserController *browser = [IOBluetoothServiceBrowserController serviceBrowserController:0];
[browser runModal];
//IOBluetoothSDPServiceRecord
IOBluetoothSDPServiceRecord *result = [[browser getResults] objectAtIndex:0];
[self describe:result];
if ([[result.device.name substringToIndex:8] isEqualToString:#"Polaroid"]) {
printer = result.device;
serviceRecord = result;
[self testPrint];
}
else {
NSLog(#"%# is not a valid device", result.device.name);
}
}
- (void) testPrint {
currentFilePath = #"/Users/oyvind/Desktop/_DSC8797.jpg";
[self sendFile:currentFilePath];
}
- (void) sendFile:(NSString *)filePath {
IOBluetoothOBEXSession *obexSession = [[IOBluetoothOBEXSession alloc] initWithSDPServiceRecord:serviceRecord];
if( obexSession != nil )
{
NSLog(#"OBEX Session Established");
OBEXFileTransferServices *fst = [OBEXFileTransferServices withOBEXSession:obexSession];
OBEXDelegate *obxd = [[OBEXDelegate alloc] init];
[obxd setFile:filePath];
[fst setDelegate:obxd];
OBEXError cnctResult = [fst connectToObjectPushService];
if( cnctResult != kIOReturnSuccess ) {
NSLog(#"Error creating connection");
return;
}
else {
NSLog(#"OBEX Session Created. Sending file: %#", filePath);
[fst sendFile:filePath];
[printer openConnection];
}
}
else {
NSLog(#"Error creating OBEX session");
NSLog(#"Error sending file");
}
}
#end
OK; here's what ultimately became the core parts of the functionality. The application I made was a sort of print server for Polaroid instant printers that would only accept images over Object Push.
First, ensure watched folder exists.
/*
Looks for a directory named PolaroidWatchFolder in the user's desktop directory
and creates it if it does not exist.
*/
- (void) ensureWatchedFolderExists {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *url = [NSURL URLWithString:#"PolaroidWatchFolder" relativeToURL:[[fileManager URLsForDirectory:NSDesktopDirectory inDomains:NSUserDomainMask] objectAtIndex:0]];
BOOL isDir;
if ([fileManager fileExistsAtPath:[url path] isDirectory:&isDir] && isDir) {
[self log:[NSString stringWithFormat:#"Watched folder exists at %#", [url absoluteURL]]];
watchFolderPath = url;
}
else {
NSError *theError = nil;
if (![fileManager createDirectoryAtURL:url withIntermediateDirectories:NO attributes:nil error:&theError]) {
[self log:[NSString stringWithFormat:#"Watched folder could not be created at %#", [url absoluteURL]]];
}
else {
watchFolderPath = url;
[self log:[NSString stringWithFormat:#"Watched folder created at %#", [url absoluteURL]]];
}
}
}
Then scan for available printers:
/*
Loops through all paired Bluetooth devices and retrieves OBEX Object Push service records
for each device who's name starts with "Polaroid".
*/
- (void) findPairedDevices {
NSArray *pairedDevices = [IOBluetoothDevice pairedDevices];
devicesTested = [NSMutableArray arrayWithCapacity:0];
for (IOBluetoothDevice *device in pairedDevices)
{
if ([self deviceQualifiesForAddOrRenew:device.name])
{
BluetoothPushDevice *pushDevice = [[BluetoothPushDevice new] initWithDevice:device];
if (pushDevice != nil)
{
[availableDevices addObject:pushDevice];
[pushDevice testConnection];
}
}
}
}
That last function call is to the BluetoothPushDevice's built-in method to test the connection. Here is the delegate handler for the response:
- (void) deviceStatusHandler: (NSNotification *)notification {
BluetoothPushDevice *device = [notification object];
NSString *status = [[notification userInfo] objectForKey:#"message"];
if ([devicesTested count] < [availableDevices count] && ![devicesTested containsObject:device.name]) {
[devicesTested addObject:device.name];
}
}
Upon server start, this method will run in response to a timer tick or manual scan:
- (void) checkWatchedFolder {
NSError *error = nil;
NSArray *properties = [NSArray arrayWithObjects: NSURLLocalizedNameKey, NSURLCreationDateKey, NSURLLocalizedTypeDescriptionKey, nil];
NSArray *files = [[NSFileManager defaultManager]
contentsOfDirectoryAtURL:watchFolderPath
includingPropertiesForKeys:properties
options:(NSDirectoryEnumerationSkipsHiddenFiles)
error:&error];
if (files == nil) {
[self log:#"Error reading watched folder"];
return;
}
if ([files count] > 0) {
int newFileCount = 0;
for (NSURL *url in files) {
if (![filesInTransit containsObject:[url path]]) {
NSLog(#"New file: %#", [url lastPathComponent]);
[self sendFile:[url path]];
newFileCount++;
}
}
}
}
When new files are found, ww first need to find a device that is not busy recieving a file of printing it:
/*
Loops through all discovered device service records and returns the a new OBEX session for
the first it finds that is not connected (meaning it is not currently in use, connections are
ad-hoc per print).
*/
- (BluetoothPushDevice*) getIdleDevice {
for (BluetoothPushDevice *device in availableDevices) {
if ([device.status isEqualToString:kBluetoothDeviceStatusReady]) {
return device;
}
}
return nil;
}
Then a file is sent with this method:
- (void) sendFile:(NSString *)filePath {
BluetoothPushDevice *device = [self getIdleDevice];
if( device != nil ) {
NSLog(#"%# is available", device.name);
if ([device sendFile:filePath]) {
[self log:[NSString stringWithFormat:#"Sending file: %#", filePath]];
[filesInTransit addObject:filePath];
}
else {
[self log:[NSString stringWithFormat:#"Error sending file: %#", filePath]];
}
}
else {
NSLog(#"No idle devices");
}
}
Upon transfer complete, this delegate method is called:
/*
Responds to BluetoothPushDevice's TransferComplete notification
*/
- (void) transferStatusHandler: (NSNotification *) notification {
NSString *status = [[notification userInfo] objectForKey:#"message"];
NSString *file = ((BluetoothPushDevice*)[notification object]).file;
if ([status isEqualToString:kBluetoothTransferStatusComplete]) {
if ([filesInTransit containsObject:file]) {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error = nil;
[fileManager removeItemAtPath:file error:&error];
if (error != nil) {
[self log:[NSString stringWithFormat:#"**ERROR** File %# could not be deleted (%#)", file, error.description]];
}
[self log:[NSString stringWithFormat:#"File deleted: %#", file]];
[filesInTransit removeObject:file];
}
else {
[self log:[NSString stringWithFormat:#"**ERROR** filesInTransit array does not contain file %#", file]];
}
}
[self updateDeviceStatusDisplay];
}
I hope this helps someone!