I am trying to use BNHtmlPdfKit to save some HTML as a PDF. Just to see if it works, I'm trying to take a web page and write it to a PDF. I'm unable to get it to work (at all). Below is my code.
First, I include the delegate reference:
#interface PPToolsTableViewController () <BNHtmlPdfKitDelegate>
Then I do the following:
NSString *exportsPath = [[PPHelpers documentsPath] stringByAppendingPathComponent:[NSString stringWithFormat:#"exports/Exported.pdf"]];
BNHtmlPdfKit *htmlPdfKit = [[BNHtmlPdfKit alloc] init];
htmlPdfKit.delegate = self;
[htmlPdfKit saveUrlAsPdf:[NSURL URLWithString:#"http://google.com"] toFile:exportsPath];
Nothing happens. No errors, and none of the delegate methods fire:
- (void) createPdf:(id)sender {
NSLog(#"Create PDF");
}
- (void)htmlPdfKit:(BNHtmlPdfKit *)htmlPdfKit didSavePdfData:(NSData *)data {
NSLog(#"PDF Save Data");
}
- (void)htmlPdfKit:(BNHtmlPdfKit *)htmlPdfKit didSavePdfFile:(NSString *)file {
NSLog(#"PDF Save File");
}
- (void)htmlPdfKit:(BNHtmlPdfKit *)htmlPdfKit didFailWithError:(NSError *)error {
NSLog(#"PDF Error");
}
Is anyone familiar with this library able to provide me with a working example? Or perhaps spot what's wrong with what I'm doing here? Thanks in advance.
I finally found this little snippet in the documentation which led me to a solution:
Be sure to retain a reference to the BNHtmlPdfKit object outside the
scope of the calling method. Otherwise, no delegate methods will be
called...
So making the BNHtmlPdfKit object a #property made all the difference:
#property (strong, nonatomic) BNHtmlPdfKit *htmlPdfKit;
...then this worked:
NSString *exportsPath = [[PPHelpers documentsPath] stringByAppendingPathComponent:[NSString stringWithFormat:#"exports/Logbook.pdf"]];
self.htmlPdfKit = [[BNHtmlPdfKit alloc] init];
self.htmlPdfKit.delegate = self;
[self.htmlPdfKit saveUrlAsPdf:[NSURL URLWithString:#"http://google.com"] toFile:exportsPath];
All better now. :)
Related
My app currently uses this deprecated function:
id unarchivedObject=[NSKeyedUnarchiver unarchiveObjectWithData:codedData];
if([unarchivedObject isKindOfClass:[NSDictionary class]]){
// currently returns TRUE when reading existing user data.
}
To update, I've converted to this:
id unarchivedObject=[NSKeyedUnarchiver unarchivedObjectOfClass:[NSDictionary class] fromData:codedData error:nil];
if([unarchivedObject isKindOfClass:[NSDictionary class]]){
// currently returns FALSE when reading existing user data.
}
The data was originally encoded like this:
-(void)encodeWithCoder:(NSCoder*)encoder{
[encoder encodeObject:text forKey:#"text"];
}
-(instancetype)initWithCoder:(NSCoder*)decoder{
if(self=[super init]){
text=[decoder decodeObjectForKey:#"text"];
}
What could be causing the IF statement to return FALSE using the newer code?
Please note that I am concerned primarily with reading existing data stored prior to deprecating the Archiving functions. Simply changing to the newer functions does not resolve the issue.
Interesting question! I've been supporting iOS 10.0 so I haven't encountered such issue until I saw this. I was tinkering for an hour and I successfully found the issue.
What could be causing the IF statement to return FALSE using the newer
code?
It's because your unarchivedObject object is nil!
If you use the parameter error in the new method, you would see an error like this:
Error Domain=NSCocoaErrorDomain Code=4864 "This decoder will only
decode classes that adopt NSSecureCoding. Class 'QTPerson' does not
adopt it." UserInfo={NSDebugDescription=This decoder will only decode
classes that adopt NSSecureCoding. Class 'QTPerson' does not adopt it.
But how do we get the correct value for this unarchivedObject and not nil? It would take a couple of steps.
First off, make your model/class conform to <NSCoding, NSSecureCoding>
Example:
QTPerson.h
#import <Foundation/Foundation.h>
#class QTPerson;
NS_ASSUME_NONNULL_BEGIN
#pragma mark - Object interfaces
#interface QTPerson : NSObject <NSCoding, NSSecureCoding>
#property (nonatomic, copy) NSString *text;
#end
NS_ASSUME_NONNULL_END
And then implement the protocol methods:
QTPerson.m
#import "QTPerson.h"
#implementation QTPerson
+ (BOOL)supportsSecureCoding {
return YES;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:_text forKey:#"text"];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self) {
_text = [coder decodeObjectOfClass:[NSString class] forKey:#"text"];
}
return self;
}
#end
And then when archiving an object, you would want to pass YES to the parameter requiringSecureCoding, like so:
QTPerson *person = [[QTPerson alloc] init];
person.text = #"Glenn";
NSData *codedData1 = [NSKeyedArchiver archivedDataWithRootObject:person requiringSecureCoding:YES error:nil];
[[NSUserDefaults standardUserDefaults] setValue:codedData1 forKey:#"boom"];
Lastly, when unarchiving, just do what you did correctly, like so:
NSData *codedData = [[NSUserDefaults standardUserDefaults] dataForKey:#"boom"];
NSError *er;
id unarchivedObject=[NSKeyedUnarchiver unarchivedObjectOfClass:[QTPerson class] fromData:codedData error:&er];
if([unarchivedObject isKindOfClass:[QTPerson class]]){
NSLog(#"TRUE!");
} else {
NSLog(#"FALSE!");
}
Voila! You'll get nonnull object unarchivedObject, hence the TRUE/YES value you're looking for!
I have an app where i want to create a temporary cache which stores key and value.I have done the following
My code is : IN appDelegate.h
#property (strong, nonatomic) NSMutableDictionary *articleCache;
In appDelegate.m
#synthesize articleCache;
and i am calling it in viewController.m
here i need to store the data so that it is cleared only when the app is terminated and is accessible anywhere in the app otherwise.
every time i visit an article i add it to the array so that next time i wont have to fetch it from the network thereby speed up the process.
the Problem is when i set the temp NSMutableDictionary the content gets added but for checkCache.articleCache i get nil.
#define DELEGATE ((AppDelegate*)[[UIApplication sharedApplication]delegate])
this is my viewDidLoad method:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
//[self loadfeeds];
[self.activityIndi startAnimating];
AppDelegate *checkCache = DELEGATE;
NSString *link = self.webUrl;
//check if the article is already opened and cached before
if([[checkCache.articleCache allKeys] containsObject:link])
{
NSLog(#"Key Exists");
NSString *contents = [checkCache.articleCache valueForKey:link];
[self loadDataOnView:contents];
}
else
{
NSOperationQueue* aQueue = [[NSOperationQueue alloc] init];
[aQueue addOperationWithBlock:^{
NSLog(#"Key not Exists");
[self startParsing];
}];
}
}
In parser method at the end i do the following i.e to store the article..
but if i add it directly to the checkCache.articleCache nothing is added what should i do?? but it gets added to temp.. do i access the articleCache incorrectly??
AppDelegate *checkCache = DELEGATE;
NSMutableDictionary *temp = [[NSMutableDictionary alloc] init];
[checkCache.articleCache setObject:Content forKey:url];
[temp setObject:Content forKey:url];
So how can i solve it??
or Suggest me how can i use NSCache for the same problem. thanks a lot.
It might be a silly question but i m quite new to ios thanks.
In App delegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.articleCache = [NSMutableDictionary new];
return YES;
}
When you have to set the object in cache.
AppDelegate *checkCache = DELEGATE;
[checkCache.articleCache setObject:obj forKey:#"Key1"];
To get the object back:
AppDelegate *checkCache = DELEGATE;
id obj = [checkCache.articleCache objectForKey:#"Key1"];
Though there are better ways to get this done.
I create a file with name File - 1.jpg on two different devices and put it in iCloud container.
I don't use UIDocument and even though I tried to use it, it does not create a conflict. Instead what I see is that documents are being automatically renamed and moved by iCloud.
So after upload one file or another becomes File - 2.jpg. All of this is fine but now I don't have a reference to file so I have no idea which is which...
Is there any way to get notified on the app side that file was renamed/moved/deleted in iCloud?
Eventually, I had to create a class that implements NSFilePresenter and point it to iCloud container folder.
Live updates from iCloud may be quite late and happen only when iCloud pulls metadata.
Also, I had to associate each created file with each device and iCloud account and persist this data, in my case in CoreData. This is where ubiquityIdentityToken becomes useful.
All file ops in iCloud container should certainly happen using NSFileCoordinator.
For add/remove events it's better to use NSMetadataQuery, NSFileCoordinator does not report those at all, but is still useful to detect when files were moved, this is what metadata query reports as updates.
This is a very basic boilerplate that can be used as a starting point:
#interface iCloudFileCoordinator () <NSFilePresenter>
#property (nonatomic) NSString *containerID;
#property (nonatomic) NSURL *containerURL;
#property (nonatomic) NSOperationQueue *operationQueue;
#end
#implementation iCloudFileCoordinator
- (instancetype)initWithContainerID:(NSString *)containerID {
self = [super init];
if(!self) {
return nil;
}
self.containerID = containerID;
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.qualityOfService = NSQualityOfServiceBackground;
[self addFilePresenter];
return self;
}
- (void)dealloc {
[self removeFilePresenter];
}
- (void)addFilePresenter {
[NSFileCoordinator addFilePresenter:self];
}
- (void)removeFilePresenter {
[NSFileCoordinator removeFilePresenter:self];
}
#pragma mark - NSFilePresenter
#pragma mark -
- (NSURL *)presentedItemURL {
NSURL *containerURL = self.containerURL;
if(containerURL) {
return containerURL;
}
NSFileManager *fileManager = [[NSFileManager alloc] init];
containerURL = [fileManager URLForUbiquityContainerIdentifier:self.containerID];
self.containerURL = containerURL;
return containerURL;
}
- (NSOperationQueue *)presentedItemOperationQueue {
return self.operationQueue;
}
- (void)presentedSubitemAtURL:(NSURL *)oldURL didMoveToURL:(NSURL *)newURL {
NSLog(#"Moved file from %# to %#", oldURL, newURL);
}
/*
... and other bunch of methods that report on sub item changes ...
*/
#end
Use CKSubscription:
Upon initialization of CKSubscription you can specify the notification options:
CKSubscriptionOptionsFiresOnRecordCreation
CKSubscriptionOptionsFiresOnRecordDeletion
CKSubscriptionOptionsFiresOnRecordUpdate
CKSubscriptionOptionsFiresOnce
iCloud Subscriptions
These looked useful for you too:
https://www.bignerdranch.com/blog/cloudkit-the-fastest-route-to-implementing-the-auto-synchronizing-app-youve-been-working-on/
http://www.raywenderlich.com/83116/beginning-cloudkit-tutorial
I'm trying to use delegate methods from NMSSH library in iOS but could not get it working. Let's take an example.
CustomViewController.h
#import <UIKit/UIKit.h>
#import <NMSSH/NMSSH.h>
#interface CustomViewController : UIViewController<NMSSHSessionDelegate, NMSSHChannelDelegate>
- (IBAction)connectButton:(UIButton *)sender;
#end
CustomViewController.m
#import "CustomViewController.h"
#implementation CustomViewController
-(void)viewDidLoad{
[super viewDidLoad];
}
- (IBAction)connectButton:(UIButton *)sender {
[self serverConnect:#"10.0.0.1"];
}
-(void)serverConnect:(NSString *)address{
NMSSHSession *session = [NMSSHSession connectToHost:address withUsername:#"username"];
NMSSHChannel *myChannel = [[NMSSHChannel alloc]init];
if (session.isConnected) {
[session authenticateByPassword:#"password"];
if (session.isAuthorized) {
NSLog(#"Authentication succeeded");
[session setDelegate:self];
[myChannel setDelegate:self];
}
}
NSError *error = nil;
//session.channel.requestPty = YES; (tried and later ignored)
NSString *response = [session.channel execute:#"mkdir" error:&error];
NSLog(#"Response from device: %#", response);
}
- (void)session:(NMSSHSession *)session didDisconnectWithError:(NSError *)error{
NSLog(#"log if session disconnects...Delegate method");
}
- (void)channel:(NMSSHChannel *)channel didReadError:(NSString *)error{
NSLog(#"Error received...Delegate method");
}
- (void)channel:(NMSSHChannel *)channel didReadRawData:(NSData *)data{
NSLog(#"Read Raw Data...Delegate method");
}
Connection to the server, sending a single line command and acknowledgement back from the server in Console is OK.
I have decent idea how to pass values from one View Controller to another using delegate (went through few tutorials with practical implementation).
With the same knowledge I am attempting to get response from delegate methods parts of NMSSH library but it's driving me round and round. I've found http://cocoadocs.org/docsets/NMSSH/2.2.1/ pretty nice API of this library but with my limited knowledge of iOS, I'm bit stuck.
Please help me.
My search finally came to an end with NMSSH AsyncAPI (branch) which supports multithreading.
I'm using Objective-C, and I don't know how to create and call a method with out parameters when compiling the code with the ARC compiler.
This is the kind of thing I'm trying to accomplish in non-ARC Objective-C (this is probably wrong anyway).
//
// Dummy.m
// OutParamTest
#import "Dummy.h"
#implementation Dummy
- (void) foo {
NSString* a = nil;
[self barOutString:&a];
NSLog(#"%#", a);
}
- (void) barOutString:(NSString **)myString {
NSString* foo = [[NSString alloc] initWithString:#"hello"];
*myString = foo;
}
#end
I've read the documentation here:
https://clang.llvm.org/docs/AutomaticReferenceCounting.html
and here:
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProperties.html
...but am finding it difficult to get anything that compiles, never mind anything that is correct. Would anybody be able to rewrite the jist of the code above, in a way that is suitable for ARC Objective-C?
You need to use the __autoreleasing attribute on the out parameter:
- (void) barOutString:(NSString * __autoreleasing *)myString {
NSString* foo = [[NSString alloc] initWithString:#"hello"];
*myString = foo;
}
The prerelease documentation (which I'm not allowed to link to due to NDA) puts the __autoreleasing in the middle of the two '*'s, but it might just work as (__autoreleasing NSString **)
You also cannot use an indirect double pointer (b) as in your original code. You must use this form:
- (void) foo {
NSString* a = nil;
[self barOutString:&a];
NSLog(#"%#", a);
}
You are also calling dealloc directly on an object which is completely wrong. I suggest you read the memory management guidelines.