How to get RTF contents from UIPasteboard after UIWebView copy? - ios

I have a UIWebView showing some custom HTML content. If I tap and hold, then select some text and tap the Copy option, the text gets added to UIPasteboard with the key "com.apple.rtfd". My problem now is that I can't find any way of extracting the actual textual contents of what I just copied. If I use this code:
NSString *contents = [NSString stringWithUTF8String:[data bytes]];
it returns the literal string "rtfd", regardless of what text I actually copied from the UIWebView. If I use this code:
NSString *contents = [[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
I get a blank string. How can I get the actual text contents of what I just copied into the pasteboard?

I have learned that when you copy selected text from a UIWebView into UIPasteboard, it actually puts multiple keyed values into the dictionary returned by UIPasteboard, of which "com.apple.rtfd" is only the first key. The actual text value of the copied element is also included under the key "public.text". This code can be used to extract the value:
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
NSArray *dataArray = [pasteboard items];
NSDictionary *dict = (NSDictionary *)[dataArray objectAtIndex:0];
NSString *pastedText;
if ([dict objectForKey:#"public.text"]) {
// this is a text paste
pastedText = (NSString *)[dict objectForKey:#"public.text"];
}

A correct Swift solution for extract rft text content what copied from Safari:
guard let rtf = textItem["public.rtf"] as? String,
let rtfData = rtf.data(using: .utf8) else {
return
}
do {
let attr = try NSAttributedString(data: rtfData,
options: [NSDocumentTypeDocumentAttribute: NSRTFTextDocumentType],
documentAttributes: nil)
//DO SOMETHING ...
}
catch (let exc) {
print(exc.localizedDescription)
}

Related

How to set a textview's text to a string created by receiving text from an app action extension

I am playing around with action extensions and looked at Apple's documents and found this code.
NSExtensionContext *myExtensionContext = self.extensionContext;
NSArray *inputItems = myExtensionContext.inputItems;
Then I change the array to a string.
NSString * resultString = [inputItems componentsJoinedByString:#""];
Then, I set the text view to the resultString string.
textView.text = resultString;
What I have been getting is
<NSExtensionItem: 0x174002840> - userInfo: {NSExtensionItemAttachmentsKey = ("<NSItemProvider: 0x17424c900> {types = (\n \"public.plain-text\"\n)}");}
that appears in my text view.
Code snippet from viewDidLoad:
[super viewDidLoad];
NSExtensionContext *myExtensionContext = self.extensionContext;
NSArray *inputItems = myExtensionContext.inputItems;
NSString * resultString = [inputItems componentsJoinedByString:#""];
textView.text = resultString;
Actually following code will return array of NSExtensionItem not a NSString type so you can not parse directly using
NSString * resultString = [inputItems componentsJoinedByString:#""];
To Parse NSArray of NSExtensionItems, You need to do following things. Here I assume that 'NSDictionary' as input type.
for (NSExtensionItem *item in self.extensionContext.inputItems) {
for (NSItemProvider *itemProvider in item.attachments) {
if ([itemProvider hasItemConformingToTypeIdentifier:#"typeIdentifier"]) {
// This is an image. We'll load it, then place it in our image view.
[itemProvider loadItemForTypeIdentifier:#"typeIdentifier" options:nil completionHandler:^(id<NSSecureCoding> item, NSError *error) {
NSDictionary* tempDict = (NSDictionary*)item;
NSLog(#"Dectionary : %#",item);
}];
}
}
}
For More details Action Extension tutorial may help you.

ios read csv file at project directory

I have implemented the module for reading the csv file
The project structure is as follows:
Project
--test_de.csv
--Folder
--Controller.h
--Controller.m
but the result shows no response. No words are added:
2014-06-19 15:32:16.817 marker[1748:60b] /var/mobile/Applications/E2B95450-429D-4777-97BE-0209522EFDEF/Documents/test_de.csv
2014-06-19 15:32:16.824 marker[1748:60b] (
)
The below is my code
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* fullPath = [paths objectAtIndex:0];
fullPath = [fullPath stringByAppendingPathComponent:#"test_de.csv"];
NSLog(#"%#", fullPath);
NSMutableArray *titleArray=[[NSMutableArray alloc]init];
NSString *fileDataString=[NSString stringWithContentsOfFile:fullPath encoding:NSUTF8StringEncoding error:nil];
NSArray *linesArray=[fileDataString componentsSeparatedByString:#"\n"];
int k=0;
for (id string in linesArray)
if(k<[linesArray count]-1){
NSString *lineString=[linesArray objectAtIndex:k];
NSLog(#"%#",lineString);
NSArray *columnArray=[lineString componentsSeparatedByString:#"\n"];
[titleArray addObject:[columnArray objectAtIndex:0]];
k++;
}
NSLog(#"%#",titleArray);
Tip: use JSON!
You are better off using a JSON format instead CVS for your resource - regardless whether this resource comes from a backend at runtime or has been embedded as an application resource at build time.
You can use the iOS system JSON decoder to parse the file into an object representation. This saves you from the many hassles to implement your own CVS parser.
Also, it should be a no-brainer to convert any input provided in CVS into JSON before you actually provide it as a resource for your application.
How to read an app resource?
Well, first ensure your resource file is included the project and has been assigned a target (see "Target Membership" in the File Inspector pane).You can also check this in the "Copy Bundle Resources" section in the "Build Phases" tab in the target editor when you select the associated target. Your file should be listed there.
Assuming, you put the given file as an application resource, you should use the Bundle class to read that resource. First get its location as a URL:
(Note: I'm only showing this in Swift and leave the Objective-C version as an exercise)
Swift:
var fileUrl = Bundle.main.url(forResource: "test_de", withExtension: "json")
This also assumes, the resource is in the root folder of the app resource folder, which happens to be the default case when you flag a file as a resource. Note, that the "app resource folder" is "opaque" to you, that is, actually you do not need to know where it is - the Bundle handles all that for you.
For Objective-C you may read here: NSBundle
Now, that you have the URL, read the file and get a Data object:
Swift:
let data = try Data(contentsOf: fileUrl)
For Objective-C you may read here: NSData
Obtain an Object Representation for your Resource
Once you have the file as data (NSData or Data), and assuming its content is a JSON Array you can get an object representation as follows:
Objective-C:
NSError *error = nil;
id jsonObject = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:&error];
if ([jsonObject isKindOfClass:[NSArray class]]) {
...
}
else {
NSLog(#"did not find a JSON Array");
...
}
Swift:
Doing it swiftly would require you to define a type (struct) which represents an element in the JSON Array. Usually, this is easy to accomplish:
struct MyElement: Codable {
let field1: String
let field2: String
let field3: String
}
Then, use the Swift JSON Decoder to get an array of MyElements:
let elements = try JSONDecoder.decode([MyElement].self, from: data)
According to SoulWithMobileTechnology this is how to do it
[self readTitleFromCSV:csvPath AtColumn:0];
And our method looks like following:
-(void)readTitleFromCSV:(NSString*)path AtColumn:(int)column
{
titleArray=[[NSMutableArray alloc]init];
NSString *fileDataString=[NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
NSArray *linesArray=[fileDataString componentsSeparatedByString:#"\n"];
int k=0;
for (id string in linesArray)
if(k<[linesArray count]-1){
NSString *lineString=[linesArray objectAtIndex:k];
NSArray *columnArray=[lineString componentsSeparatedByString:#";"];
[titleArray addObject:[columnArray objectAtIndex:column]];
k++;
}
NSLog(#"%#",titleArray);
}

How to convert formatted content of NSTextView to string

I need transfer content of NSTextView from Mac app to iOS app. I'm using XML as transfered file format.
So I need to save content of NSTextView (text, fonts, colors atd.) as a string. Is there any way how to do that?
One way to do this is to archive the NSAttributedString value. Outline sample code typed directly into answer:
NSTextView *myTextView;
NSString *myFilename;
...
[NSKeyedarchiver archiveRootObject:myTextStorage.textStorage
toFile:myFilename];
To read it back:
myTextView.textStorage.attributedString = [NSKeyedUnarchiver unarchiveObjectWithFile:myFilename];
That's all that is needed to create and read back a file. There are matching methods which create an NSData rather than a file, and you can convert an NSData into an NSString or just insert one into an NSDictionary and serialise that as a plist (XML), etc.
Your best bet is probably to store the text as RFTD and load it as such in the other text view via an NSAttributedString.
// Load
NSFileWrapper* filewrapper = [[NSFileWrapper alloc] initWithPath: path];
NSTextAttachment *attachment = [[NSTextAttachment alloc] initWithFileWrapper: filewrapper];
NSAttributedString* origFile = [NSAttributedString attributedStringWithAttachment: attachment];
// Save
NSData *data = [origFile RTFDFromRange: NSMakeRange(0, [origFile length]) documentAttributes: nil];
[[NSFileManager defaultManager] createFileAtPath: path contents: data attributes:nil];

Copy NSAttributedString in UIPasteBoard

How do you copy an NSAttributedString in the pasteboard, to allow the user to paste, or to paste programmatically (with - (void)paste:(id)sender, from UIResponderStandardEditActions protocol).
I tried:
UIPasteboard *pasteBoard = [UIPasteboard generalPasteboard];
[pasteBoard setValue:attributedString forPasteboardType:(NSString *)kUTTypeRTF];
but this crash with:
-[UIPasteboard setValue:forPasteboardType:]: value is not a valid property list type'
which is to be expected, because NSAttributedString is not a property list value.
If the user paste the content of the pasteboard in my app, I would like to keep all the standards and custom attributes of the attributed string.
I have found that when I (as a user of the application) copy rich text from a UITextView into the pasteboard, the pasteboard contains two types:
"public.text",
"Apple Web Archive pasteboard type
Based on that, I created a convenient category on UIPasteboard.
(With heavy use of code from this answer).
It works, but:
The conversion to html format means I will lose custom attributes. Any clean solution will be gladly accepted.
File UIPasteboard+AttributedString.h:
#interface UIPasteboard (AttributedString)
- (void) setAttributedString:(NSAttributedString *)attributedString;
#end
File UIPasteboard+AttributedString.m:
#import <MobileCoreServices/UTCoreTypes.h>
#import "UIPasteboard+AttributedString.h"
#implementation UIPasteboard (AttributedString)
- (void) setAttributedString:(NSAttributedString *)attributedString {
NSString *htmlString = [attributedString htmlString]; // This uses DTCoreText category NSAttributedString+HTML - https://github.com/Cocoanetics/DTCoreText
NSDictionary *resourceDictionary = #{ #"WebResourceData" : [htmlString dataUsingEncoding:NSUTF8StringEncoding],
#"WebResourceFrameName": #"",
#"WebResourceMIMEType" : #"text/html",
#"WebResourceTextEncodingName" : #"UTF-8",
#"WebResourceURL" : #"about:blank" };
NSDictionary *htmlItem = #{ (NSString *)kUTTypeText : [attributedString string],
#"Apple Web Archive pasteboard type" : #{ #"WebMainResource" : resourceDictionary } };
[self setItems:#[ htmlItem ]];
}
#end
Only implemented setter. If you want to write the getter, and/or put it on GitHub, be my guest :)
Instead of involving HTML, the clean solution is to insert NSAttributedString as RTF (plus plaintext fallback) into the paste board:
- (void)setAttributedString:(NSAttributedString *)attributedString {
NSData *rtf = [attributedString dataFromRange:NSMakeRange(0, attributedString.length)
documentAttributes:#{NSDocumentTypeDocumentAttribute: NSRTFTextDocumentType}
error:nil];
self.items = #[#{(id)kUTTypeRTF: [[NSString alloc] initWithData:rtf encoding:NSUTF8StringEncoding],
(id)kUTTypeUTF8PlainText: attributedString.string}];
}
Swift 5
import MobileCoreServices
public extension UIPasteboard {
func set(attributedString: NSAttributedString) {
do {
let rtf = try attributedString.data(from: NSMakeRange(0, attributedString.length), documentAttributes: [NSAttributedString.DocumentAttributeKey.documentType: NSAttributedString.DocumentType.rtf])
items = [[kUTTypeRTF as String: NSString(data: rtf, encoding: String.Encoding.utf8.rawValue)!, kUTTypeUTF8PlainText as String: attributedString.string]]
} catch {
}
}
}
It is quite simple:
#import <MobileCoreServices/UTCoreTypes.h>
NSMutableDictionary *item = [[NSMutableDictionary alloc] init];
NSData *rtf = [attributedString dataFromRange:NSMakeRange(0, attributedString.length)
documentAttributes:#{NSDocumentTypeDocumentAttribute: NSRTFDTextDocumentType}
error:nil];
if (rtf) {
[item setObject:rtf forKey:(id)kUTTypeFlatRTFD];
}
[item setObject:attributedString.string forKey:(id)kUTTypeUTF8PlainText];
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
pasteboard.items = #[item];
The pasteboard manager in OSX can auto convert between a lot of textual and image types.
For rich text types, you'd usually place RTF into the pasteboard. You can create RTF representation from an attributed string, and vice versa. See the "NSAttributedString Application Kit Additions Reference".
If you have images included as well, then use the RTFd instead of RTF flavor.
I don't know the MIME types for these (I'm used to the Carbon Pasteboard API, not the Cocoa one), but you can convert between UTIs, Pboard and MIME Types using the UTType API.
UTI for RTF is "public.rtf", for RTFd it's "com.apple.flat-rtfd".

How do a write and read a string value to/from UIPasteboard in iOS?

I have the following test, failing
NSString * expectedValue = #"achilles";
UIPasteboard *pasteboard = [UIPasteboard pasteboardWithName:#"pb1" create:YES];
pasteboard.persistent = YES;
pasteboard.string = expectedValue;
STAssertEqualObjects(expectedValue, [pasteboard string], #"get written value from pasteboard");
[pasteboard setString:expectedValue];
STAssertEqualObjects(expectedValue, [pasteboard string], #"get written value from pasteboard");
Both of the asserts fail,
'achilles' should be equal to '(null)'
Am I incorrectly writing to the pasteboard, reading from the pasteboard, or both?
I assume the problem is the following:
The string property of UIPasteboard is defined as (see the docs)
#property(nonatomic, copy) NSString *string;
This means that if you assign a string to it, it will be copied, i.e. a new object is created. When you read it back and compare it to the original object, the compare must fail.
If you are writing or reading a string from UIPasteBoard you can easily do it by accessing,
[UIPasteboard generalPasteboard].string = #"your string";
NSString *str = [UIPasteboard generalPasteboard].string];
For reading and writing NSString you can use general paste board.
there are two simple ways to write...
//Method 1
NSString * str=#"your String";
UIPasteboard * pasteboard=[UIPasteboard generalPasteboard];
[pasteboard setString:];
//Method 2
NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
[pasteboard setData:data forPasteboardType:(NSString *)kUTTypeText];
and you can read NSString as bellow
UIPasteboard * pasteboard=[UIPasteboard generalPasteboard];
//Method 1
NSLog(#"Text =%#",[pasteboard string]);
//Method 2
NSData * data = [pasteboard dataForPasteboardType:(NSString*)kUTTypeText];
NSString * str =[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"str =%#",str);
For more info you can refer this Blog
Your code passed the testing in Unit Testing. You should point out your development environment if you did have trouble in it.
Test Case '-[TestTests testExample]' started.
Test Case '-[TestTests testExample]' passed (0.001 seconds).
Tested in Xcode 5.1.1 with SDK iOS7.1

Resources