I'm trying to configure an Open Graph post. I've followed the code examples on the FB developer site, and, using a test_user, a post is supposedly successfully generated. Here is my code:
- (void)createOGObjectForImage:(NSURL *)imageURL
{
NSMutableDictionary<FBGraphObject> *object =
[FBGraphObject openGraphObjectForPostWithType:#"ogminigame:mini_game"
title:#"Mini Game"
image:#"https://fbstatic-a.akamaihd.net/images/devsite/attachment_blank.png"
url:imageURL
description:#""];
[FBRequestConnection startForPostWithGraphPath:#"me"
graphObject:object
completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if(!error) {
NSLog(#"Result: %#", [result description]);
[self createOGActionWithResult:result];
} else {
NSLog(#"Error posting the Open Graph object to the Object API: %#", error);
[self sharePhotoWithShareDialog:self openGraphAction:nil];
}
}];
}
However, nothing is appearing on the test user wall. When I step through the code, I can see that when I create the OG object, the result has the following contents:
"FACEBOOK_NON_JSON_RESULT" = true;
More specifically, it looks like this:
So when I create my Action, when I try to retrieve the objectID using:
NSString *objectId = [result objectForKey:#"id"];
it obviously returns nil. Am I missing a stage with the result object? I've tried searching for similar problems but there doesn't seem to be much in the way of an explanation.
Well, it seems the code supplied on the Facebook developer site where you supply your custom stories is incorrect. Namely, it should look like:
- (void)createOGObjectForImage:(NSURL *)imageURL
{
NSMutableDictionary<FBOpenGraphObject> *object = [FBGraphObject openGraphObjectForPostWithType:#"ogminigame:mini_game"
title:#"Mini Game"
image:#"https://fbstatic-a.akamaihd.net/images/devsite/attachment_blank.png"
url:imageURL
description:#""];
[FBRequestConnection startForPostOpenGraphObject:object completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if(!error) {
NSLog(#"Result: %#", [result description]);
[self createOGActionWithResult:result];
} else {
NSLog(#"Error posting the Open Graph object to the Object API: %#", error);
[self sharePhotoWithShareDialog:self openGraphAction:nil];
}
}];
}
So you cast the dictionary to an FBOpenGraphObject, rather than an FBGraphObject and call startForPostOpenGraphObject instead of startForPostWithGraphPath.
It seems to me Facebook need to be a bit more consistent with their documentation.
I still have nothing showing on the test_account page, but at least the above doesn't seem to be the cause of the issue...
So I'm retrieving an object that has a file as one of it's fields. In the Parse.com data browser the file is there and downloads. However, when I retrieve the object, PFFile *wordlistFile = [object objectForKey:kWSWordlistFilesFileKey]; is returning null, and so getDataInBackgroundWithBlock does nothing.
Here's the log of the object I'm retrieving. There is no reference to the file:
2013-12-03 12:07:10.635 WSPhoto[24958:a0b] object = <WordlistFiles:lRHFmHaPRg:(null)> {
ACL = "<PFACL: 0xd445670>";
language = Spanish;
}
And here's the full code. It appears I'm doing everything correctly based on some examples I've seen:
PFQuery* wordlistFilesQuery = [PFQuery queryWithClassName:kWSWordlistFilesClassKey];
[wordlistFilesQuery whereKey:kWSWordlistFilesLanguageKey equalTo:language];
[wordlistFilesQuery includeKey:kWSWordlistFilesFileKey];
[wordlistFilesQuery setCachePolicy:kPFCachePolicyNetworkOnly];
[wordlistFilesQuery getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (!error) {
PFFile *wordlistFile = [object objectForKey:kWSWordlistFilesFileKey];
NSLog(#"******* wordlistFile = %#",wordlistFile);
// Show HUD view
AppDelegate *appDel = (AppDelegate *)[[UIApplication sharedApplication]delegate];
[appDel showGlobalProgressHUDWithTitle:#"Loading wordlist. This may take a while."];
[wordlistFile getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if (!error) {
// Super private stuff here
}
// The data didn't load
else {
NSLog(#"loadWordlistFromDBByFile -- wordlist does not exist, loading by querying");
[self loadWordlistFromDBByQuery:language];
}
} progressBlock:^(int percentDone) {
}];
}
// The object didn't load
else {
NSLog(#"loadWordlistFromDBByFile -- wordlist does not exist, loading by querying");
[self loadWordlistFromDBByQuery:language];
}
}];
Make sure your kWSWordlistFilesFileKey is exactly the same as the name of the column seen on the data browser on Parse.com.
So if the column is called "wordlistfile", make sure:
kWSWordlistFilesFileKey = #"wordlistfile";
OK, this was a silly error. I reloaded the WordlistFiles class in the Parse.com Data Browser, and the file disappeared. Not sure how it happened. I swear to you guys I was staring it in the face. I re-uploaded and now it's retrieving, and there doesn't seem to be any weird behavior where it's deleting it.
Operator error.
Hello I am using the SDWebImage framework in a project and I want to download and cache images, but I think my code is storing an image in the cache twice? Is there any way to store the image in the cache by a key only once? Here is my code.
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadWithURL:[NSURL URLWithString:url] options:0 progress:^(NSUInteger receivedSize, long long expectedSize) {
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) {
if(image){
NSString *localKey = [NSString stringWithFormat:#"Item-%d", i];
[[SDImageCache sharedImageCache] storeImage:image forKey:localKey];
}
}];
Is there something that I missed? Looks like doing this in my allocations instrument is pilling up a lot of memory.
I'm surprised nobody answered this question, but I've had a similar question and came across this, so I'll answer it for people viewing this going forward (assuming you've sorted this out yourself by now).
To directly answer your question, yes, you are caching the image twice.
Download calls to SDWebImageManager automatically cache images with keys based on the absoluteString of the image's url. If you want your own key, you can use the download call on SDWebImageDownloader which as far as I can tell does NOT cache by default. From there you can call the sharedImageCache as you're already doing and cache with whatever key you want.
That aside, it is strange you're seeing allocations piling up in any case as SDWebImage likes to cache to disk and not memory generally. Maybe something else is going on at the same time?
In Swift use the code below to download an image and to store it in the cache:
//SDWebImageManager download image with High Priority
SDWebImageManager.sharedManager().downloadImageWithURL(NSURL(string: imageUrl), options: SDWebImageOptions.HighPriority, progress: { (receivedSize :Int, ExpectedSize :Int) in
SVProgressHUD.show()
}, completed: { (image :UIImage!, error:NSError!, cacheType :SDImageCacheType, finished :Bool,imageUrl: NSURL!) in
if(finished) {
SVProgressHUD.dismiss()
if((image) != nil) {
//image downloaded do your stuff
}
}
})
Swift 3 version:
SDWebImageManager.shared().downloadImage(with: NSURL(string: "...") as URL!, options: .continueInBackground, progress: {
(receivedSize :Int, ExpectedSize :Int) in
}, completed: {
(image : UIImage?, error : Error?, cacheType : SDImageCacheType, finished : Bool, url : URL?) in
})
Objective-C version:
[[SDWebImageDownloader sharedDownloader] downloadImageWithURL:[NSURL URLWithString:imageUrl] options:SDWebImageDownloaderUseNSURLCache progress:nil completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
if (image && finished) {
// Cache image to disk or memory
[[SDImageCache sharedImageCache] storeImage:image forKey:CUSTOM_KEY toDisk:YES];
}
}];
SWIFT 5 & Latest SDWebImage 5.2.3
SDWebImageManager.shared.loadImage(
with: album.artUrlFor(imageShape: .square),
options: .continueInBackground, // or .highPriority
progress: nil,
completed: { [weak self] (image, data, error, cacheType, finished, url) in
guard let sself = self else { return }
if let err = error {
// Do something with the error
return
}
guard let img = image else {
// No image handle this error
return
}
// Do something with image
}
)
Swift 4:
SDWebImageManager.shared().loadImage(
with: URL(string: imageUrl),
options: .highPriority,
progress: nil) { (image, data, error, cacheType, isFinished, imageUrl) in
print(isFinished)
}
SDWebImage caches the image both to disk as well as memory. Let's go through this:
You download the image from a new url.
It gets cached to memory and disk.
If you call the image in the same session, it is retrieved from the memory.
Let's say you re-run the app and then access the url, it will check the memory, where the image won't be there, then it will check the disk, and get it from there. If not, it will download it.
The image stays in disk for a week by the standard setting.
So, you don't need to worry about caching. SDWebImage takes care of it pretty damn well.
You can do custom implementation for caching as well as image refresh from the cache in case you want the settings as per your HTTP caching header as well.
You can find the complete details on their github page here.
Swift 4.0.
let url = URL(string: imageUrl!)
SDWebImageManager.shared().imageDownloader?.downloadImage(with: url, options: .continueInBackground, progress: nil, completed: {(image:UIImage?, data:Data?, error:Error?, finished:Bool) in
if image != nil {.
}
})
Try APSmartStorage (https://github.com/Alterplay/APSmartStorage) instead of SDWebImage.
APSmartStorage gets data from network and automatically caches data on disk or in memory in a smart configurable way. Should be good enough for your task.
Yes is possible to download the image using SDWebImage And store into local memory Manually.
Downloaded Image store into local memory using SDWebImage
func saveImage(url: URL, toCache: UIImage?, complation: #escaping SDWebImageNoParamsBlock) {
guard let toCache = toCache else { return }
let manager = SDWebImageManager.shared()
if let key = manager.cacheKey(for: url) {
manager.imageCache?.store(toCache, forKey: key, completion: complation)
}
}
Load image from memory using image URL
static func imageFromMemory(for url: String) -> UIImage? {
if let encoded = url.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed),
let url = URL(string: encoded) {
let manager = SDWebImageManager.shared()
if let key: String = manager.cacheKey(for: url),
let image = manager.imageCache?.imageFromMemoryCache(forKey: key) {
return image
}
}
return nil
}
//CREATE A CUSTOME IMAGEVIEW AND PASS THE IMAGE URL BY ARRAY(INDEXPATH.ROW)
(void) loadImage:(NSString *)imageLink{
imageLink = [imageLink stringByReplacingOccurrencesOfString:#" " withString:#"%20"];
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager loadImageWithURL:[NSURL URLWithString:imageLink] options:SDWebImageDelayPlaceholder progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
} completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
imageFrame.image = image;
}];
}
Swift 5.1
import UIKit
import SDWebImage
extension UIImageView{
func downloadImage(url:String){
//remove space if a url contains.
let stringWithoutWhitespace = url.replacingOccurrences(of: " ", with: "%20", options: .regularExpression)
self.sd_imageIndicator = SDWebImageActivityIndicator.gray
self.sd_setImage(with: URL(string: stringWithoutWhitespace), placeholderImage: UIImage())
}
}
How to use
let myUrl = "https://www.example.com"
myImageView.downloadImage(url: myUrl)
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadImageWithURL:ImageUrl options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize)
{
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if(image){
NSLog(#"image=====%#",image);
}
}];
Looking for some advice on stablizing my app. First some requirements - files with PII (personally identifying information) must be encrypted when on disk. Tumbnails and logos are in the custom TableViewCells (if available) and must be decrypted before display.
There are several layers of threading going on. There is a central function getFileData that checks to see if files are on the device or if the files need to be obtained from the network. I desire to keep the UI responsive and (I think) therein lies my problem.
Here is some code:
This is the workhorse method for processing files in my application. It decides where the file is decrypts it and hands it back to a callback:
-(void)fetchFileData:(UserSession *) session
onComplete: (void(^)(NSData* data)) onComplete
{
NSURL *url = [File urlForMail:self.fileId andSession:session];
//NSLog(#"File id: %#", self.fileId);
NSString *encryptionKey = session.encryptionKey;
dispatch_queue_t cryptoQ = dispatch_queue_create(FILE_CRYPTOGRAPHY_QUEUE, NULL);
dispatch_async(cryptoQ, ^(void){
// Get the file and d/encrypt it
NSError *error = nil;
if ([File fileExistsAtUrl:url] == YES) {
NSLog(#"file is on disk.");
NSData *localEncryptedFile = [File getDataForFile:url];
NSData *decryptedFile = [RNDecryptor decryptData:localEncryptedFile
withPassword:encryptionKey
error:&error];
onComplete(decryptedFile);
dispatch_release(cryptoQ);
} else {
//NSLog(#"File is not on disk");
NSDictionary *remoteFile = [session.apiFetcher getFileContent:self.fileId
andToken:session.token];
if (remoteFile && [[remoteFile objectForKey:#"success"] isEqualToString:#"true"]) {
NSData *remoteFileData = [remoteFile objectForKey:#"data"];
NSString *mimeType = [remoteFile objectForKey:#"mimeType"];
self.mimeType = mimeType;
NSData *encryptedData = [RNEncryptor encryptData:remoteFileData
withSettings:kRNCryptorAES256Settings
password:encryptionKey
error:&error];
[encryptedData writeToURL:url atomically:YES];
onComplete(remoteFileData);
dispatch_release(cryptoQ);
}
}
});
Here is an example of a getFileData caller:
+(void)loadThumbnailForMail: (NSNumber*)thumbnailId
session: (UserSession*)session
callback: (void(^)(NSData* data))callback
{
File *file = [File findFile:thumbnailId inContext:session.mailDatabase.managedObjectContext];
dispatch_queue_t fetchQ = dispatch_queue_create(FILE_FETCHER_QUEUE_LABEL, NULL);
dispatch_async(fetchQ, ^(void) {
if (file) {
[file fetchFileData:session onComplete:^(NSData *data) {
if (data && file.mimeType) {
callback(data);
}
}];
}
});
dispatch_release(fetchQ);
}
Here is an example of the TableViewCell that is calling loadThumbnailForMail:
-(void)loadAndShowThumbnailImage:(Mail*) mail
{
UIImage *placeHolder = [UIImage imageNamed:#"thumbnail_placeholder.png"];
[self.thumbnailImageForMail setImage:placeHolder];
dispatch_queue_t loaderQ = dispatch_queue_create(THUMBNAIL_FETCHER, NULL);
dispatch_async(loaderQ, ^ {
[File loadThumbnailForMail: mail.thumbnailId
session: [UserSession instance]
callback: ^(NSData *data) {
dispatch_async(dispatch_get_main_queue(), ^{
UIImage *thumbnailImage = [UIImage imageWithData:data];
[self.thumbnailImageForMail setImage:thumbnailImage];
});
}];
});
dispatch_release(loaderQ);
}
I think that my issue here is the callback in my loadThumbnailImage. If the user scrolls fast enough I suspect that there could be two threads trying to access the same TableViewCell
(MyCell *cell = (MyCell*)[tableView dequeueReusableCellWithIdentifier:CellTableIdentifier];)
It doesn't always happen right away but eventually, after some scrolling the tableView list of cells the app crashes with this: * Terminating app due to uncaught exception 'NSGenericException', reason: '* Collection <__NSCFSet: 0xde6a650> was mutated while being enumerated.'
I need to have the decrypted images in the cells and the first solution (above) does this for me when the images are available but causes the app to crash. I am wondering if some sort of in memory cache would help improve this if I put images in memory there when they were decrypted and checked that cache in loadAndShowThumbnailImage before I kick off all the threads to get and decrypt them.
Thoughts? I have been banging on this for a week now trying different things and would appreciate some fresh perspective.
Thanks.
Based on Justins link and subsequent research I ended up going in this direction: In a UITableView, best method to cancel GCD operations for cells that have gone off screen?
I'm creating an app with iCloud. But I have some problem. It creates directory on iCloud using NSFileWrapper, then it creates NSData (container) file in NSFileWrapper directory. I'm using this code to convert NSFileWrapper to NSMutableArray:
NSFileWrapper *MyWrapper=[[[MyDocument data] fileWrappers] objectForKey:#"myFile.doh"];
NSData *MyData=[NSData dataWithData:[MyWrapper regularFileContents]];
NSMutableArray *MyList=[NSPropertyListSerialization propertyListFromData:MyData mutabilityOption:NSPropertyListMutableContainers format:nil errorDescription:nil];
And it works correctly only on the device, which has created this container. On other devices the result of this code is BAD_ACCESS (in the second line of the code, where I start doing something with data). While debugging, function "regularFileContents" returns correct object with correct data size, but when I try to read this data, BAD_ACEESS(code=10) happens.
I'm using ARC, so it's not an error of memory management.
May be the problem is in some project/code sign settings? Any ideas?
Thanks!
I ran into this as well and after much experimentation I've found that even though the outer wrapper has downloaded the inner contents have not actually downloaded yet and that causes the call to regularFileContents to fail.
I've been calling startDownloadingUbiquitousItemAtURL on MyWrapper and once that completes the error is gone. Here's a method that checks the downloaded status of a file (assuming you know the url to your MyWrapper) and starts the download if it isn't downloaded yet.
-(BOOL)downloadFileIfNotAvailable:(NSURL*)fileURL
{
NSNumber *isInCloud = nil;
if ([fileURL getResourceValue:&isInCloud forKey:NSURLIsUbiquitousItemKey error:nil])
{
if ([isInCloud boolValue]) {
NSNumber *isDownloaded = nil;
if ([fileURL getResourceValue:&isDownloaded forKey:NSURLUbiquitousItemIsDownloadedKey error:nil])
{
if ([isDownloaded boolValue])
{
return YES;
}
NSError *error = nil;
[[NSFileManager defaultManager] startDownloadingUbiquitousItemAtURL:fileURL error:&error];
if (error)
{
NSLog(#"Download Failed :: %#", error);
}
return NO;
}
}
}
return YES;
}