macOS 10.13: "Scheduling the NSURLDownload loader is no longer supported." - cocoa

Running my macOS app in macOS 10.13, I see printed to the console:
Scheduling the NSURLDownload loader is no longer supported.
What does this mean?

The Sparkle Updater seems to be the culprit in the instances I have found. I guess the Sparkle dev team will be on to it and hopefully we'll no longer see the message after Sparkle is updated.

It appears to mean You have just created an instance of the deprecated class NSURLDownload.
To show this, create a new Cocoa command-line tool project in Xcode and replace the code in main.m with the following:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
#autoreleasepool {
NSURL* url = [[NSURL alloc] initWithString:#"https://example.com"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:30.0];
NSLog(#"Will print strange sentence to console") ;
[[NSURLDownload alloc] initWithRequest:request
delegate:nil];
NSLog(#"Did print strange sentence to console") ;
}
return 0;
}
Build and run. I get the following result in console (timestamps removed):
Will print strange sentence to console:
Scheduling the NSURLDownload loader is no longer supported.
Did print strange sentence to console
I would say the "fix" is to replace the deprecated NSURLDownload with NSURLSession.

You can correct it directly in the source code for Sparkle. Update SUAppcast.m file at line 82 by replace the NSURLDownload with the following:
NSURLSessionDownloadTask *downloadTask = [[NSURLSession sharedSession] downloadTaskWithRequest:request completionHandler:^(NSURL *location, __unused NSURLResponse *response, NSError *error) {
if (location) {
NSString *destinationFilename = NSTemporaryDirectory();
if (destinationFilename) {
// The file will not persist if not moved, Sparkle will remove it later.
destinationFilename = [destinationFilename stringByAppendingPathComponent:#"Appcast.xml"];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *anError = nil;
NSString *fromPath = [location path];
if ([fileManager fileExistsAtPath:destinationFilename])
[fileManager removeItemAtPath:destinationFilename error:&anError];
BOOL fileCopied = [fileManager moveItemAtPath:fromPath toPath:destinationFilename error:&anError];
if (fileCopied == NO) {
[self reportError:anError];
} else {
self.downloadFilename = destinationFilename;
dispatch_async(dispatch_get_main_queue(), ^{
[self downloadDidFinish:[[NSURLDownload alloc] init]];
});
}
}
} else {
[self reportError:error];
}
}];
[downloadTask resume];

Related

MacOS Mojave UNNotificationAttachment thumbnail not showing up in notifications

This is the (simplified) code I am trying to run. The image file exists and the app does not crash. What happens in that the image is deleted from the location as mentioned in the documentation for UserNotifications. However, the image thumbnail does not show up in the notification that is generated.
I am not sure what else I am missing here.
#import <UserNotifications/UserNotifications.h>
int main(int argc, const char * argv[]) {
UNUserNotificationCenter* notificationCenter = [UNUserNotificationCenter currentNotificationCenter];
[notificationCenter requestAuthorizationWithOptions:(UNAuthorizationOptionAlert + UNAuthorizationOptionSound)
completionHandler:^(BOOL granted, NSError * _Nullable error) {}
];
UNMutableNotificationContent *localNotification = [UNMutableNotificationContent new];
localNotification.title = [NSString localizedUserNotificationStringForKey:#"Title" arguments:nil];
localNotification.body = [NSString localizedUserNotificationStringForKey:#"Body Text" arguments:nil];
localNotification.sound = [UNNotificationSound defaultSound];
NSString *path = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES)lastObject];
NSString *testUrl = [path stringByAppendingPathComponent:#"imageFile.jpg"];
NSURL* url = [NSURL fileURLWithPath:testUrl];
CGRect rect = CGRectMake(0.25, 0.25, 0.75, 0.75);
NSDictionary* options = # {
#"UNNotificationAttachmentOptionsTypeHintKey": (__bridge NSString*) kUTTypeJPEG,
#"UNNotificationOptionsThumbnailHiddenKey" : #NO,
#"UNNotificationAttachmentOptionsThumbnailClippingRectKey": [NSValue valueWithRect: rect]
};
UNNotificationAttachment* imageAttachment = [UNNotificationAttachment attachmentWithIdentifier:#""
URL:url
options:options
error:nil];
localNotification.attachments=#[imageAttachment];
UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1 repeats:NO];
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:#"Identifier" content: localNotification trigger:trigger];
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
[center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
NSLog(#"Notification created");
}];
[NSThread sleepForTimeInterval:100.0f];
return 0;
}
Check if you set up the key NSUserNotificationAlertStyle
Check the system response((BOOL granted, NSError * _Nullable error)) if you get an error, please provide it.
Add notification in the completion block.
Do not run your code in main, app might haven't been properly initialized, use AppDelegate appDidFinishLaunching instead.
Test with triggerWithTimeInterval:[NSDate date].timeIntervalSince1970+30 (it contradicts documentation but works in my case)
Collapse you app in delivery time, it might be not delivered when app is in focus.
Also go to "System Preferences" and check if your app is allowed to post notifications.(If you do not manage to find your app in the list, please, let us know, it might be the key to the problem)

How to use hardcoded file path names with sandbox

Ok, yes I know now that you can not use hardcoded paths with sandbox. Up to this point I have not delt with sandbox, so I never encountered it.
I have a Coredata App (Mac OSx) and I used the default save code and the default path location (user/...../applicationsupport/... This, of coarse, is not acceptable in the sandbox.
Without requiring the user to manually open the data file each time the program is launched, is there another way to deal with this?
I would appreciate any input/suggestions.
Thanks You
Sandbox doesn't mean there isn't any access to files and folders without user selection. As it said in App Sandbox in Depth article there's container directory you still having access to.
For taking a path to your Application Support-directory you should use the same code whenever you use Sandboxing or not.
+ (NSString *)executableName
{
NSString *executableName = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleExecutable"];
if(!executableName || executableName.length==0)
return nil;
return executableName;
}
- (NSString *)findOrCreateDirectory:(NSSearchPathDirectory)searchPathDirectory
inDomain:(NSSearchPathDomainMask)domainMask
appendPathComponent:(NSString *)appendComponent
error:(NSError **)errorOut
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(searchPathDirectory,domainMask,YES);
if ([paths count]==0)
return nil;
NSString *resolvedPath = [paths objectAtIndex:0];
if (appendComponent)
resolvedPath = [resolvedPath stringByAppendingPathComponent:appendComponent];
NSError *error;
BOOL success = [self createDirectoryAtPath:resolvedPath withIntermediateDirectories:YES attributes:nil error:&error];
if (!success)
{
if (errorOut)
*errorOut = error;
return nil;
}
return resolvedPath;
}
- (NSString *)applicationSupportDirectory
{
NSError *error;
NSString *result = [self findOrCreateDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask
appendPathComponent:[self executableName] error:&error];
if (error)
return nil;
return result;
}

How to make an OSX app use a sqlite database in the bundle?

I have an app that is intended to ship with a pre-populated sqlite database. I will include that database on the bundle. How do I modify the delegate to use that database instead of generating a new one?
This is my first serious app on OSX.
The current methods on the delegate is like this right now:
// Returns the directory the application uses to store the Core Data store file. This code uses a directory named "com.addfone.LoteriaMac" in the user's Application Support directory.
- (NSURL *)applicationFilesDirectory
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *appSupportURL = [[fileManager URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] lastObject];
return [appSupportURL URLByAppendingPathComponent:#"com.myApp.BotMax"];
}
// Creates if necessary and returns the managed object model for the application.
- (NSManagedObjectModel *)managedObjectModel
{
if (_managedObjectModel) {
return _managedObjectModel;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"BotMax" withExtension:#"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return _managedObjectModel;
}
// Returns the persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it. (The directory for the store is created, if necessary.)
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator) {
return _persistentStoreCoordinator;
}
NSManagedObjectModel *mom = [self managedObjectModel];
if (!mom) {
NSLog(#"%#:%# No model to generate a store from", [self class], NSStringFromSelector(_cmd));
return nil;
}
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *applicationFilesDirectory = [self applicationFilesDirectory];
NSError *error = nil;
NSDictionary *properties = [applicationFilesDirectory resourceValuesForKeys:#[NSURLIsDirectoryKey] error:&error];
if (!properties) {
BOOL ok = NO;
if ([error code] == NSFileReadNoSuchFileError) {
ok = [fileManager createDirectoryAtPath:[applicationFilesDirectory path] withIntermediateDirectories:YES attributes:nil error:&error];
}
if (!ok) {
[[NSApplication sharedApplication] presentError:error];
return nil;
}
} else {
if (![properties[NSURLIsDirectoryKey] boolValue]) {
// Customize and localize this error.
NSString *failureDescription = [NSString stringWithFormat:#"Expected a folder to store application data, found a file (%#).", [applicationFilesDirectory path]];
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setValue:failureDescription forKey:NSLocalizedDescriptionKey];
error = [NSError errorWithDomain:#"YOUR_ERROR_DOMAIN" code:101 userInfo:dict];
[[NSApplication sharedApplication] presentError:error];
return nil;
}
}
NSURL *url = [applicationFilesDirectory URLByAppendingPathComponent:#"BotMax.storedata"];
NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
if (![coordinator addPersistentStoreWithType:NSXMLStoreType configuration:nil URL:url options:nil error:&error]) {
[[NSApplication sharedApplication] presentError:error];
return nil;
}
_persistentStoreCoordinator = coordinator;
return _persistentStoreCoordinator;
}
The database file is named like BotMax.sqlite
thanks.
The simplest way to ship a pre-filled SQLite DB would be to copy the .sqlite file into the Application Support folder before the persistentStoreCoordinator is initialised.
The below code just copies the bundled DB. There are some things to consider before using that approach:
This simple approach only copies the initial DB once.
It does not consider merging or migrating existing stores (e.g when you ship an update with a changed model)
Core Data is using a new journal_mode since iOS 7 & OS X 10.9. This mode creates an additional file when saving the DB. (*.sqlite-wal). If the program that creates your pre-filled SQLite file was linked against OS X 10.9/iOS 7 or later SDKs, you'll also have to ship the .sqlite-wal file.
Alternatively, you can disable the WAL journal mode in the app that creates your pre-filled DB by passing #{ NSSQLitePragmasOption : #{ #"journal_mode" : #"DELETE" } }; as option dict to addPersistentStoreWithType
The following code is based on the standard Xcode template for non-document based Core Data apps:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator) {
return _persistentStoreCoordinator;
}
NSString* dbFilename = #"BotMax.sqlite";
NSURL* initialDBURL = [[[NSBundle mainBundle] resourceURL] URLByAppendingPathComponent:dbFilename];
NSURL* workingDBURL = [[self applicationFilesDirectory] URLByAppendingPathComponent:dbFilename];
NSFileManager* fileManager = [NSFileManager defaultManager];
NSURL* applicationFilesDirectory = [self applicationFilesDirectory];
NSError* error = nil;
if(![fileManager fileExistsAtPath:[workingDBURL path]])
{
BOOL result = [fileManager copyItemAtURL:initialDBURL toURL:workingDBURL error:&error];
if(!result)
{
[[NSApplication sharedApplication] presentError:error];
return nil;
}
}
...
}
objc.io issue #4 has a section about shipping pre-filled Core Data stores: http://www.objc.io/issue-4/importing-large-data-sets-into-core-data.html

didReceiveData only getting called once on resuming download

I have a problem which I could really do with some help with please
I am using NSURLconnection to download a large file (27MB). the code work fine when there is no network interruption. In order to allow for network issues and only partially downloaded file I have added code to check to see how much of the file is downloaded and then using a server request to download the missing portion.
The code works as it should IF I download part of file, stop the program running and then run again - the download then commences where it left off and i have complete file.
However if I hit the download button a second time without stopping the program then didReceiveData only gets called once and adds just 200KB to the file and it tells me file has been succesfully downloaded.
Help please - I have spent ages trying to figure out what I'm doing wrong.
Code below:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
NSLog(#"Response code = %d",httpResponse.statusCode );
file = [NSFileHandle fileHandleForUpdatingAtPath:filename] ;// file is in .h
if (file) {
[file seekToEndOfFile];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
if (file) {
[file seekToEndOfFile];
NSLog(#"file is %#",file);
}
[self.file writeData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
if([[NSFileManager defaultManager] fileExistsAtPath:filename])
{
[file closeFile];
file = nil;
theConnection = nil;
filename = nil;
theRequest = nil;
}
NSLog(#"Connection failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[file closeFile];
file = nil;
theConnection = nil;
filename = nil;
}
- (IBAction)downloadFile:(id)sender {
filename = [[NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:movie1]; // filename is in .h file
theRequest=[NSMutableURLRequest requestWithURL:[NSURL URLWithString:movieDownload1] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
NSUInteger downloadedBytes = 0;
NSFileManager *fm = [NSFileManager defaultManager];
if ([fm fileExistsAtPath:filename]) {
NSError *error = nil;
NSDictionary *fileDictionary = [fm attributesOfItemAtPath:filename error:&error];
if (!error && fileDictionary)
downloadedBytes = [fileDictionary fileSize];
} else {
[fm createFileAtPath:filename contents:nil attributes:nil];
}
if (downloadedBytes > 0) {
NSString *requestRange = [NSString stringWithFormat:#"bytes=%d-",downloadedBytes];
[theRequest setValue:requestRange forHTTPHeaderField:#"Range"];
}
theConnection = nil;
theConnection = [NSURLConnection connectionWithRequest:theRequest delegate:self];
}
Instead of using seekToEndOfFile in didReceiveResponse and didReceiveData, you can try the following code snippet. It worked well for me.
- (id)initWithURL:(NSString *)downloadString
{
if (![super init])
return nil;
// Construct the URL to be downloaded
self.downloadURL = [[NSURL alloc]initWithString:downloadString];
self.downloadData = [[NSMutableData alloc] init];
self.downloadedFilename = [[self.downloadURL path] lastPathComponent];
[self downloadFile];
return self;
}
-(void) downloadFile
{
// set the filePath
docFolderPath = [NSHomeDirectory() stringByAppendingPathComponent: [NSString stringWithFormat: #"Documents/%#", self.downloadedFilename]];
self.downloadStream = [NSOutputStream outputStreamToFileAtPath:docFolderPath append:NO];
if (!self.downloadStream)
{
self.error = [NSError errorWithDomain:[NSBundle mainBundle].bundleIdentifier
code:-1
userInfo:#{#"message": #"Unable to create NSOutputStream", #"function" : #(__FUNCTION__), #"path" : self.downloadedFilename}];
return;
}
[self.downloadStream open];
self.downloadConnection = [[NSURLConnection alloc] initWithRequest:downloadRequest delegate:self];
[self.downloadConnection start];
}
//code snippet for the Resume functionality after your downloading gets paused/cancel
-(void) resumeInterruptedDownload
{
NSFileManager *fm = [NSFileManager defaultManager];
if ([fm fileExistsAtPath:docFolderPath])
{
NSError *error = nil;
NSDictionary *fileDictionary = [fm attributesOfItemAtPath:docFolderPath
error:&error];
if (!error && fileDictionary)
self.downloadedBytes = [fileDictionary fileSize];
} else
{
[fm createFileAtPath:docFolderPath contents:nil attributes:nil];
}
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:self.downloadURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0];
// Define the bytes we wish to download.
if(self.downloadedBytes != 0)
{
NSString *range = [NSString stringWithFormat:#"bytes=%i-", self.downloadedBytes];
[request setValue:range forHTTPHeaderField:#"Range"];
}
// Data should immediately start downloading after the connection is created.
self.downloadConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:TRUE];
}
It worked perfectly for me in case of the large files in Mbs but for the small files in Kbs it sometimes fails. You don't have to try much in all these delegate methods of NSURLConnection. One thing you can do is that set the macros for each state i.e, cancel, pause, downloading,downloaded so that you can come to know that when do you want to resume the downloading. Also you can try the following http://nachbaur.com/blog/resuming-large-downloads-with-nsurlconnection.
I know it is too late to reply, but I just got into IOS. If you try this, please let me know whether it worked or not. Thanks :) :)

Saving a PDF file through a UIWebView so that is visible (in the same WebView) even offline

I have this question that is driving me crazy: could I save a PDF file (that is online) locally on an iOS Device so that I can view the file every time even offline in the same UIWebView?
If I could, how?
If you have control over such UIWebView and the URL it opens, then yes, you can. See the following code as example. It uses the ASIHttpRequest library, which you can find at http://allseeing-i.com/ASIHTTPRequest/
#import "WebViewPDFViewController.h"
#import <CommonCrypto/CommonDigest.h>
#import "ASIHTTPRequest.h"
#implementation WebViewPDFViewController
-(NSString *) md5:(NSString *) str {
const char *cStr = [str UTF8String];
unsigned char result[16];
CC_MD5( cStr, strlen(cStr), result );
return [NSString stringWithFormat:
#"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
result[0], result[1], result[2], result[3],
result[4], result[5], result[6], result[7],
result[8], result[9], result[10], result[11],
result[12], result[13], result[14], result[15]
];
}
-(void) viewWillAppear:(BOOL)animated {
NSString *pdfOnlinePath = #"http://redinter.eu/web/files/revistas/2Dummy.pdf";
// Check if the file is already stored locally. If it is not, then first
// download it, and load from the local cache. Next requests will always
// load from the local cache
NSString *pdfHash = [self md5:pdfOnlinePath];
NSString *pdfCachePath = [NSString stringWithFormat:#"%#/pdfcache_%#.pdf",
[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0],
pdfHash];
if ([[NSFileManager defaultManager] fileExistsAtPath:pdfCachePath]) {
NSLog(#"Cached file found, using it");
}
else {
// Not found in the local cache, download and store it
NSLog(#"File not found in the local cache, going to download it");
ASIHTTPRequest *downloadRequest = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:pdfOnlinePath]];
[downloadRequest setDownloadDestinationPath:pdfCachePath];
[downloadRequest startSynchronous];
}
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL fileURLWithPath:pdfCachePath]];
UIWebView *webView = [[[UIWebView alloc] initWithFrame:self.view.bounds] autorelease];
[webView loadRequest:request];
[self.view addSubview:webView];
}
- (void)dealloc {
[super dealloc];
}
#end
I have tested it here ant it works fine.

Resources