facebook / facebook/facebook-ios-sdk

FBSDKShareDialog unable to show when sharing video

Open
#2,525 3 comments 0 reactions 0 assignees View on GitHub
bug needs-triage
Dominant language
Swift
Stars
8.1k
Forks
3.7k
PR merge metrics
No merged PRs in 30d

Description

### Checklist before submitting a bug report

- [X] I've updated to the latest released version of the SDK
- [X] I've searched for existing [GitHub issues](https://github.com/facebook/facebook-ios-sdk/issues)
- [X] I've looked for existing answers on [Stack Overflow](https://facebook.stackoverflow.com), the [Facebook Developer Community Forum](https://developers.facebook.com/community/) and the [Facebook Developers Group](https://www.facebook.com/groups/fbdevelopers)
- [X] I've read the [Code of Conduct](https://github.com/facebook/facebook-ios-sdk/blob/main/CODE_OF_CONDUCT.md)
- [X] This issue is not security related and can safely be disclosed publicly on GitHub

### Xcode version

16.1.0

### Facebook iOS SDK version

17.4.0

### Dependency Manager

CocoaPods

### SDK Framework

Other / I don't know

### Goals

Able to share video

### Expected results

Open Facebook app to share video, and return back to our app.

### Actual results

Unable to show FBSDKShareDialog

### Steps to reproduce

Using the sample code, call shareRemoteVideo or shareVideoFromGallery to reproduce the issue

### Code samples & details

```swift
#import
#import
#import
#import
#import
#import
#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) FBSDKLoginManager *loginManager;
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
self.loginManager = [[FBSDKLoginManager alloc] init];
}

- (IBAction)loginButtonPressed:(id)sender {
[self.loginManager logInWithPermissions:@[@"public_profile", @"email"]
fromViewController:self
handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) {
if (error) {
NSLog(@"Login failed: %@", error.localizedDescription);
return;
}

if (result.isCancelled) {
NSLog(@"Login cancelled");
return;
}

NSLog(@"Login successful");
}];
}

- (IBAction)shareButtonPressed:(id)sender {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Share Options"
message:@"Choose what to share"
preferredStyle:UIAlertControllerStyleActionSheet];

[alertController addAction:[UIAlertAction actionWithTitle:@"Share Link"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
[self shareLink];
}]];

[alertController addAction:[UIAlertAction actionWithTitle:@"Share Photo"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
[self sharePhoto];
}]];

[alertController addAction:[UIAlertAction actionWithTitle:@"Share Remote Video"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
NSURL *videoURL = [NSURL URLWithString:@"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"];
[self shareRemoteVideo:videoURL];
}]];

[alertController addAction:[UIAlertAction actionWithTitle:@"Share Gallery Video"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
[self shareVideoFromGallery];
}]];

[alertController addAction:[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:nil]];

if ([sender isKindOfClass:[UIView class]]) {
alertController.popoverPresentationController.sourceView = sender;
alertController.popoverPresentationController.sourceRect = [(UIView *)sender bounds];
}

[self presentViewController:alertController animated:YES completion:nil];
}

- (void)shareLink {
FBSDKShareLinkContent *content = [[FBSDKShareLinkContent alloc] init];
content.contentURL = [NSURL URLWithString:@"https://www.example.com"];
[self showShareDialogWithContent:content];
}

- (void)shareRemoteVideo:(NSURL *)videoURL {
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:videoURL
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"Video download failed: %@", error.localizedDescription);
return;
}

NSString *tempPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
tempPath = [tempPath stringByAppendingPathExtension:@"mp4"];
NSURL *tempURL = [NSURL fileURLWithPath:tempPath];

NSError *fileMoveError = nil;
BOOL moved = [[NSFileManager defaultManager] moveItemAtURL:location toURL:tempURL error:&fileMoveError];

if (!moved) {
NSLog(@"Failed to move downloaded file: %@", fileMoveError.localizedDescription);
return;
}

[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
[PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:tempURL];
} completionHandler:^(BOOL success, NSError *error) {
if (!success) {
NSLog(@"Failed to save video: %@", error.localizedDescription);
return;
}

// Clean up the temporary file after saving
[[NSFileManager defaultManager] removeItemAtURL:tempURL error:nil];

PHFetchOptions *fetchOptions = [[PHFetchOptions alloc] init];
fetchOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
PHFetchResult *fetchResult = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeVideo options:fetchOptions];

if (fetchResult.count > 0) {
PHAsset *videoAsset = fetchResult.firstObject;
dispatch_async(dispatch_get_main_queue(), ^{
[self shareVideoWithPHAsset:videoAsset];
});
}
}];
}];

[downloadTask resume];
}

- (void)shareVideoFromGallery {
[self checkPhotoLibraryPermissionWithCompletion:^(BOOL granted) {
if (granted) {
[self openVideoPicker];
} else {
NSLog(@"Photo library permission denied");
}
}];
}

- (void)checkPhotoLibraryPermissionWithCompletion:(void(^)(BOOL granted))completion {
PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];

if (status == PHAuthorizationStatusNotDetermined) {
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus newStatus) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(newStatus == PHAuthorizationStatusAuthorized);
});
}];
} else {
completion(status == PHAuthorizationStatusAuthorized);
}
}

- (void)openVideoPicker {
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.delegate = self;
picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
picker.mediaTypes = @[(NSString *)kUTTypeMovie];
picker.allowsEditing = NO;

[self presentViewController:picker animated:YES completion:nil];
}

- (void)shareVideoWithPHAsset:(PHAsset *)asset {
if (asset.mediaType != PHAssetMediaTypeVideo) {
[self showErrorAlert:@"Selected asset is not a video"];
return;
}

PHVideoRequestOptions *options = [[PHVideoRequestOptions alloc] init];
options.version = PHVideoRequestOptionsVersionCurrent;
options.deliveryMode = PHVideoRequestOptionsDeliveryModeHighQualityFormat;
options.networkAccessAllowed = YES;

[[PHImageManager defaultManager] requestAVAssetForVideo:asset
options:options
resultHandler:^(AVAsset *avAsset, AVAudioMix *audioMix, NSDictionary *info) {
if ([avAsset isKindOfClass:[AVURLAsset class]]) {
AVURLAsset *urlAsset = (AVURLAsset *)avAsset;
[self validateAndShareVideo:urlAsset withAsset:asset];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[self showErrorAlert:@"Failed to process video"];
});
}
}];
}

- (void)validateAndShareVideo:(AVURLAsset *)urlAsset withAsset:(PHAsset *)asset {
NSURL *videoURL = urlAsset.URL;
NSError *error = nil;

// Get file attributes
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:videoURL.path error:&error];
if (error) {
dispatch_async(dispatch_get_main_queue(), ^{
[self showErrorAlert:@"Failed to get video file information"];
});
return;
}

// Check file size (10GB = 10 * 1024 * 1024 * 1024 bytes)
unsigned long long fileSize = [fileAttributes fileSize];
const unsigned long long maxFileSize = 10ull * 1024ull * 1024ull * 1024ull; // 10GB

if (fileSize > maxFileSize) {
dispatch_async(dispatch_get_main_queue(), ^{
[self showErrorAlert:@"Video file size exceeds Facebook's 10GB limit"];
});
return;
}

// Get video track
AVAssetTrack *videoTrack = [[urlAsset tracksWithMediaType:AVMediaTypeVideo] firstObject];
if (!videoTrack) {
dispatch_async(dispatch_get_main_queue(), ^{
[self showErrorAlert:@"Invalid video format"];
});
return;
}

// Check duration (240 minutes = 14400 seconds)
CMTime duration = urlAsset.duration;
float durationInSeconds = CMTimeGetSeconds(duration);
const float maxDuration = 14400.0f; // 240 minutes

if (durationInSeconds > maxDuration) {
dispatch_async(dispatch_get_main_queue(), ^{
[self showErrorAlert:@"Video duration exceeds Facebook's 240 minutes limit"];
});
return;
}

// Log video information for debugging
CGSize dimensions = [videoTrack naturalSize];
NSLog(@"Video validation:");
NSLog(@"File size: %llu bytes (%.2f MB)", fileSize, fileSize/1024.0f/1024.0f);
NSLog(@"Duration: %.2f seconds (%.2f minutes)", durationInSeconds, durationInSeconds/60.0f);
NSLog(@"Dimensions: %.0f x %.0f", dimensions.width, dimensions.height);
NSLog(@"URL: %@", videoURL);

// If all validations pass, proceed with sharing
dispatch_async(dispatch_get_main_queue(), ^{
[self generateThumbnailForAsset:asset completion:^(UIImage *previewImage) {
FBSDKShareVideo *video = [[FBSDKShareVideo alloc] initWithVideoURL:videoURL previewPhoto:nil];
FBSDKShareVideoContent *content = [[FBSDKShareVideoContent alloc] init];
[content setVideo:video];

FBSDKShareDialog *dialog = [[FBSDKShareDialog alloc] initWithViewController:self content:content delegate:self];
dialog.mode = FBSDKShareDialogModeAutomatic;

BOOL didShowDialog = NO;
NSError *error = nil;
BOOL isValid = [dialog validateWithError:&error];

if (!isValid) {
NSLog(@"Validation failed with error: %@", error.localizedDescription);
}

if ([dialog canShow]) {
didShowDialog = YES;
[dialog show];
}

if (!didShowDialog) {
[self showErrorAlert:@"Could not show share dialog. Please try again."];
}
}];
});
}

- (void)generateThumbnailForAsset:(PHAsset *)asset completion:(void(^)(UIImage *thumbnail))completion {
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
options.synchronous = NO;
options.networkAccessAllowed = YES;

CGSize targetSize = CGSizeMake(1200, 1200);

[[PHImageManager defaultManager] requestImageForAsset:asset
targetSize:targetSize
contentMode:PHImageContentModeAspectFit
options:options
resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
BOOL degraded = [[info objectForKey:PHImageResultIsDegradedKey] boolValue];
if (!degraded) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(result);
});
}
}];
}

- (void)showErrorAlert:(NSString *)message {
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Error"
message:message
preferredStyle:UIAlertControllerStyleAlert];

[alert addAction:[UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleDefault
handler:nil]];

[self presentViewController:alert animated:YES completion:nil];
});
}

- (void)showShareDialogWithContent:(id)content {
FBSDKShareDialog *dialog = [[FBSDKShareDialog alloc] initWithViewController:self content:content delegate:self];
dialog.mode = FBSDKShareDialogModeNative;

if ([dialog canShow]) {
[dialog show];
} else {
NSLog(@"Cannot show share dialog");
}
}

- (void)sharePhoto {
[self loadImageFromURL:@"https://fastly.picsum.photos/id/1/200/300.jpg?hmac=jH5bDkLr6Tgy3oAg5khKCHeunZMHq0ehBZr6vGifPLY" completion:^(UIImage *image, NSError *error) {
if (error) {
NSLog(@"Failed to load image: %@", error.localizedDescription);
} else {
FBSDKSharePhotoContent *photoContent = [[FBSDKSharePhotoContent alloc] init];
photoContent.contentURL = [NSURL URLWithString:@"https://www.google.com"];

FBSDKSharePhoto *photo = [[FBSDKSharePhoto alloc] initWithImage:image isUserGenerated:YES];
photoContent.photos = @[photo];

[self showShareDialogWithContent:photoContent];
}
}];
}

- (void)loadImageFromURL:(NSString *)imageURL completion:(void (^)(UIImage *image, NSError *error))completion {
// Create a URL object from the image URL string
NSURL *url = [NSURL URLWithString:imageURL];
if (!url) {
if (completion) {
NSError *error = [NSError errorWithDomain:@"com.yourapp.error" code:-1001 userInfo:@{NSLocalizedDescriptionKey : @"Invalid URL"}];
completion(nil, error);
}
return;
}

// Create a data task to download the image
NSURLSessionDataTask *downloadTask = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

// Check for any download errors
if (error) {
if (completion) {
completion(nil, error);
}
return;
}

// Check if data was received
if (data) {
// Create a UIImage from the downloaded data
UIImage *image = [UIImage imageWithData:data];

// Validate the image data
if (image) {
// Return the image if valid
if (completion) {
completion(image, nil);
}
} else {
// Return an error if the image is invalid
NSError *imageError = [NSError errorWithDomain:@"com.yourapp.error" code:-1002 userInfo:@{NSLocalizedDescriptionKey : @"Invalid image data"}];
if (completion) {
completion(nil, imageError);
}
}
} else {
// Handle the case where no data was received
NSError *noDataError = [NSError errorWithDomain:@"com.yourapp.error" code:-1003 userInfo:@{NSLocalizedDescriptionKey : @"No data received"}];
if (completion) {
completion(nil, noDataError);
}
}
}];

// Start the download task
[downloadTask resume];
}

#pragma mark - UIImagePickerControllerDelegate
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
[picker dismissViewControllerAnimated:YES completion:nil];

NSURL *videoURL = info[UIImagePickerControllerReferenceURL];
PHFetchResult *result = [PHAsset fetchAssetsWithALAssetURLs:@[videoURL] options:nil];

if (result.count > 0) {
[self shareVideoWithPHAsset:result.firstObject];
}
}

- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
[picker dismissViewControllerAnimated:YES completion:nil];
}

#pragma mark - FBSDKSharingDelegate
- (void)sharer:(id)sharer didCompleteWithResults:(NSDictionary *)results {
NSLog(@"Share completed: %@", results);
}

- (void)sharer:(id)sharer didFailWithError:(NSError *)error {
NSLog(@"Share failed: %@", error);
}

- (void)sharerDidCancel:(id)sharer {
NSLog(@"Share cancelled");
}

@end
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.